Live data from Hacker News

Playing with Go: Embarrassingly Parallel Scripts

collectiveidea.com

41–50 of 53 posts

Re: Playing with Go: Embarrassingly Parallel Scripts

#41

Earlier quoted context omitted.

So many ignored errors. :(

This is one of the things I love about go. If he wants the return value but doesn't want to address the errors, he has to actively do it. It makes you think twice when you are putting a _ for an error return. And now to another programmer coming in to maintain it, they stick out like a sore thumb.

I know zero Go (it's a little far down on the to-learn list), and maybe he edited it, but I went back and read the GP's code and didn't see anything that looked like ignoring errors.

Is it the two ", _" things?

Re: Playing with Go: Embarrassingly Parallel Scripts

#42

Earlier quoted context omitted.

This is one of the things I love about go. If he wants the return value but doesn't want to address the errors, he has to actively do it. It makes you think twice when you are putting a _ for an error return. And now to another programmer coming in to maintain it, they stick out like a sore thumb.

I know zero Go (it's a little far down on the to-learn list), and maybe he edited it, but I went back and read the GP's code and didn't see anything that looked like ignoring errors. Is it the two ", _" things?

So for this line of code:

file_in, _ := ioutil.ReadFile("domains.txt")

It says call ReadFile. In go a function can return multiple values. ReadFile returns an byte array and an error value. Normally you'd check to see if err is nil, if it is then no error happened. If it isn't you can check it out for information about the error and handle it.

A unique feature about go is that declaring a variable and never using it is an error and not a warning. Actually there aren't even compiler warnings. Either it is right or wrong. This means if he had called:

file_in, err := ioutil.ReadFile("domains.txt")

but never checked err it would not build. So to get the byte array but not the error you use the _ symbol to tell it to throw away that return value. This is what I was on about in that you have to actively ignore error handling if you want a return value.

Re: Playing with Go: Embarrassingly Parallel Scripts

#43
post #3
post #2

Yes, Go does make writing stuff like that nice, but I think the actual code given is pretty heavyweight. This does the same thing: package main import ( "fmt" "net" "io/ioutil" "strings" ) func main() { file_in, _ := ioutil.ReadFile("domains.txt") domain_list := string(file_in) done := make(chan bool) count := 0 for _, domain := range strings.Split(strings.TrimSpace(domain_list), "\n") { go func(d string) { ipAddress…

There's a library for that pattern: http://golang.org/pkg/sync/#WaitGroup Instead of counting and using a done channel, import "sync" ... var wg sync.WaitGroup for ... { wg.Add(1) go dowork() } wg.Wait()

Even so why would you write that boilerplate code out each time?

Something like this would work even better:

    result = src.asyncMap { |e| dowork(e) };
Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is. And with no generics it would be annoying to actually use e and the result, since they would need casts. Too bad.

Re: Playing with Go: Embarrassingly Parallel Scripts

#44
post #23

My attempt with Ruby and Celluloid. https://gist.github.com/a803d86234e8d1fc5496 I also include a list of 100 domains in a domains.txt if anyone wants to try for themselves. require "socket" require "celluloid" class IPGetter include Celluloid def get(url) Socket.getaddrinfo(url, "http")[0][2] end end pool = IPGetter.pool(size: 100) ips = {} File.open("domains.txt").each_line do |line| line.chomp! ips[line] = pool.fu…

In Scala using the par function: https://gist.github.com/4199415

    def ip(host:String)=java.net.InetAddress.getAllByName(host)(0).getHostAddress
    var map = Map[String,String]()
    val hosts = List("google.com","twitter.com","facebook.com")
    hosts.par.map(host => (host,ip(host))).foreach(hostip => map+=(hostip._1->hostip._2))

    scala> map
    res1: scala.collection.immutable.Map[String,String]
     =Map(google.com -> 74.125.224.71,
          twitter.com -> 199.59.150.7, 
          facebook.com -> 69.171.229.16)

Re: Playing with Go: Embarrassingly Parallel Scripts

#45

Earlier quoted context omitted.

I know zero Go (it's a little far down on the to-learn list), and maybe he edited it, but I went back and read the GP's code and didn't see anything that looked like ignoring errors. Is it the two ", _" things?

So for this line of code: file_in, _ := ioutil.ReadFile("domains.txt") It says call ReadFile. In go a function can return multiple values. ReadFile returns an byte array and an error value. Normally you'd check to see if err is nil, if it is then no error happened. If it isn't you can check it out for information about the error and handle it. A unique feature about go is that declaring a variable and never using it…

It seems tantalising for the compiler to also protest when return values of type error are not assigned to anything. An obvious inconvenience being that use of fmt.Println and similar would suddenly become noisy.

Re: Playing with Go: Embarrassingly Parallel Scripts

#46
post #3

Earlier quoted context omitted.

There's a library for that pattern: http://golang.org/pkg/sync/#WaitGroup Instead of counting and using a done channel, import "sync" ... var wg sync.WaitGroup for ... { wg.Add(1) go dowork() } wg.Wait()

Even so why would you write that boilerplate code out each time? Something like this would work even better: result = src.asyncMap { |e| dowork(e) }; Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is. And with no generics it would be annoying to actually use e and the result, since they would need casts. Too bad.

Why am I not surprised to see you trolling HN too? What's all too sweet is that you've brought your anti-Go zealotry here too! Joy.

> Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is.

This is false. Functions in Go do not have to return multiple values. Therefore, you can "collect all the results as-is".

> And with no generics it would be annoying to actually use e and the result, since they would need casts. Too bad.

Actually, it wouldn't be annoying, because you wouldn't use a general purpose map like you've shown. You'd use code shown in the parent.

Re: Playing with Go: Embarrassingly Parallel Scripts

#47

Earlier quoted context omitted.

Even so why would you write that boilerplate code out each time? Something like this would work even better: result = src.asyncMap { |e| dowork(e) }; Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is. And with no generics it would be annoying to actually use e and the result, since they would need casts. Too bad.

Why am I not surprised to see you trolling HN too? What's all too sweet is that you've brought your anti-Go zealotry here too! Joy. > Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is. This is false. Functions in Go do not have to return multiple values. Therefore, you can "collect all the results as-is". > And with no generics it would be annoying to actually…

If you think you can do asyncMap in Google Go, without casts and without manually collecting multiple return values, by all means show us the code. I would find that really interesting.

Re: Playing with Go: Embarrassingly Parallel Scripts

#48
post #23

My attempt with Ruby and Celluloid. https://gist.github.com/a803d86234e8d1fc5496 I also include a list of 100 domains in a domains.txt if anyone wants to try for themselves. require "socket" require "celluloid" class IPGetter include Celluloid def get(url) Socket.getaddrinfo(url, "http")[0][2] end end pool = IPGetter.pool(size: 100) ips = {} File.open("domains.txt").each_line do |line| line.chomp! ips[line] = pool.fu…

How about a few lines of C?

  // clang -lcares -I/opt/local/include -L/opt/local/lib -o domains domains.c
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  #include 
  
  static void callback(void *arg, int status, int timeouts, struct hostent *host)
  {
  
      if(!host || status != ARES_SUCCESS){
          printf("Failed to lookup %s\n", ares_strerror(status));
          return;
      }
  
      char ip[INET6_ADDRSTRLEN];
      int i = 0;
  
      for (i = 0; host->h_addr_list[i]; ++i) {
          inet_ntop(host->h_addrtype, host->h_addr_list[i], ip, sizeof(ip));
          printf("%s: %s\n", host->h_name, ip);
      }
  }
  
  static void wait_ares(ares_channel channel)
  {
      for(;;){
          struct timeval *tvp, tv;
          fd_set read_fds, write_fds;
          int nfds;
  
          FD_ZERO(&read_fds);
          FD_ZERO(&write_fds);
          nfds = ares_fds(channel, &read_fds, &write_fds);
          if(nfds == 0){
              break;
          }
          tvp = ares_timeout(channel, NULL, &tv);
          select(nfds, &read_fds, &write_fds, NULL, tvp);
          ares_process(channel, &read_fds, &write_fds);
      }
  }
  
  struct Request {
      ares_channel channel;
      char* domain;
      SLIST_ENTRY(Request) next;
  };
  
  int main(const int argc, const char* argv[]) {
      SLIST_HEAD(Requests, Request) requests;
      SLIST_INIT(&requests);
      struct Request* last = NULL;
  
      int status;
      struct ares_options options;
      int optmask = 0;
      const char* path;
  
      optmask |= ARES_OPT_TIMEOUTMS;
      options.timeout = 1000;
  
      status = ares_library_init(ARES_LIB_INIT_ALL);
      if (status != ARES_SUCCESS){
          printf("ares_library_init: %s\n", ares_strerror(status));
          return 1;
      }
  
      for (int i = 1; (path = argv[i]) != NULL || i == 0; i++) {
          
          int fd = open(path, O_RDONLY);
          FILE* fp = fdopen(fd, "r");
          char* buffer = NULL;
          size_t bufferSize = 0;
          int lineLength = 0;
          
          while ((lineLength = getline(&buffer, &bufferSize, fp)) > 0) {
  
              struct Request* request = malloc(sizeof(struct Request));
  
              status = ares_init_options(&request->channel, &options, optmask);
              if(status != ARES_SUCCESS) {
                  printf("ares_init_options: %s\n", ares_strerror(status));
                  continue;
              }
              
              buffer[lineLength - 1] = '\0';
  
              request->domain = buffer;
              buffer = NULL;
              if (NULL == last) {
                  SLIST_INSERT_HEAD(&requests, request, next);
              } else {
                  SLIST_INSERT_AFTER(last, request, next);
              }
              last = request;
  
              ares_gethostbyname(request->channel, request->domain, AF_INET, callback, NULL);
          }
      }
  
      while (!SLIST_EMPTY(&requests)) {
          struct Request* request = SLIST_FIRST(&requests);
          wait_ares(request->channel);
          ares_destroy(request->channel);
          free(request->domain);
          SLIST_REMOVE_HEAD(&requests, next);
          free(request);
      }
  
      ares_library_cleanup();
      return 0;
  }

Re: Playing with Go: Embarrassingly Parallel Scripts

#49

Earlier quoted context omitted.

Why am I not surprised to see you trolling HN too? What's all too sweet is that you've brought your anti-Go zealotry here too! Joy. > Except Google Go returns several values instead of tuples, so you can't just collect all the results as-is. This is false. Functions in Go do not have to return multiple values. Therefore, you can "collect all the results as-is". > And with no generics it would be annoying to actually…

If you think you can do asyncMap in Google Go, without casts and without manually collecting multiple return values, by all means show us the code. I would find that really interesting.

I didn't claim I could. Take your trolling elsewhere.

Re: Playing with Go: Embarrassingly Parallel Scripts

#50
post #11

Earlier quoted context omitted.

I know it's about Go, but bash is awesome for stuff like this: while read line; do host $line | head -n1 | awk '{print $1 " -> " $4}' & done

I realize it's a one-off, but you could potentially mix lines of output with a script like that. If you gnu parallel (xargs++) installed - turn that second line into a script (and replace $line with $1). cat domains.txt | parallel -n 1 -P 50 script.sh

If you are looking for which domain point to 1.2.3.4:

   cat domains.txt | parallel -P 100 --tag host | grep 1.2.3.4
Somewhat slower runtime than the go-solution, but may be faster to write.
Post reply on HN