2015-09-25 3 views
1

Я пытаюсь установить время, необходимое для выполнения параллельных запросов. Мои результаты времени примерно в четыре раза медленнее, чем сообщает ab2.Сроки запросов в goroutines

Я пробовал запросы на синхронизацию двумя разными способами, обе из которых приводят к аналогичным результатам (которые далеки от результатов ab2). Чтобы привести пример, ab2 сообщит, что самый длинный запрос длится 2 миллисекунды на локальном сервере, тогда как этот код будет сообщать до 4,5 миллисекунд. Кстати, вся база кода доступна here.

Как правильно правильно запросить время?

Метод 1: выбор времени включает в себя больше, чем просто просьбу

this commit С.

// Let's spawn all the requests, with their respective concurrency. 
wg.Add(r.Repeat) 
r.doneWg.Add(r.Repeat) 
for rno := 1; rno <= r.Repeat; rno++ { 
    go func(no int, greq goreq.Request) { 
     r.ongoingReqs <- struct{}{} // Adding sentinel value to limit concurrency. 
     startTime := time.Now() 
     greq.Uri = r.URL.Generate() 
     gresp, err := greq.Do() 
     if err != nil { 
      log.Critical("could not send request to #%d %s: %s", no, r.URL, err) 
     } else if no < r.Repeat { 
      // We're always using the last response for the next batch of requests. 
      gresp.Body.Close() 
     } 
     <-r.ongoingReqs // We're done, let's make room for the next request. 
     resp := Response{Response: gresp, duration: time.Now().Sub(startTime)} 
     // Let's add that request to the list of completed requests. 
     r.doneChan <- &resp 
     runtime.Gosched() 
    }(rno, greq) 
} 

Способ 2: использование внутренней функции с Defer заявление

От this commit.

// Let's spawn all the requests, with their respective concurrency. 
wg.Add(r.Repeat) 
r.doneWg.Add(r.Repeat) 
for rno := 1; rno <= r.Repeat; rno++ { 
    go func(no int, greq goreq.Request) { 
     r.ongoingReqs <- struct{}{} // Adding sentinel value to limit concurrency. 
     greq.Uri = r.URL.Generate() 
     var duration time.Duration 
     gresp, err := func(dur *time.Duration) (gresp *goreq.Response, err error) { 
      defer func(startTime time.Time) { *dur = time.Now().Sub(startTime) }(time.Now()) 
      return greq.Do() 
     }(&duration) 

     if err != nil { 
      log.Critical("could not send request to #%d %s: %s", no, r.URL, err) 
     } else if no < r.Repeat { 
      // We're always using the last response for the next batch of requests. 
      gresp.Body.Close() 
     } 
     <-r.ongoingReqs // We're done, let's make room for the next request. 
     resp := Response{Response: gresp, duration: duration} 
     // Let's add that request to the list of completed requests. 
     r.doneChan <- &resp 
     runtime.Gosched() 
    }(rno, greq) 
} 

Я смотрел this question, что не помогло.

+0

Если вы пытаетесь создать тест, добавление другого слоя поверх пакета http с «goreq» кажется контрпродуктивным. Сначала попробуйте со стандартным пакетом http, а затем профиль, если еще не пропало время. Ряд генераторов нагрузки HTTP был написан в Go с хорошими результатами. – JimB

+0

Сервер вообще возвращает * любой * контент в теле ответа? – JimB

+0

@JimB, да сервер может вернуть контент. И этот генератор нагрузки может фактически использовать возвращенные данные для последующих запросов. – ChrisR

ответ

1

Как ваши goroutines входят в syscall (запись в сокет), они выгружаются. Это означает, что они прерываются, и другой горутин будет работать на их месте. В конце концов, ваш выгруженный goroutine будет запланирован снова, и он продолжит работу там, где он остановился. Однако это не обязательно происходит после того, как выполняется syscall.

Сроки в горуте сложно, потому что, даже если вы сделали все последовательно, сборщик мусора Go 1.5 будет периодически запускать, прерывая теоретический последовательный цикл.

Единственным реальным решением является немного сложным:

  1. Использование RawSyscall непосредственно.
  2. Аннотируйте свою функцию с помощью //go:nosplit, чтобы предотвратить ее выгрузку.
  3. Отключить сборщик мусора.

Даже тогда я мог бы что-то забыть.

+0

Спасибо за ваш быстрый ответ. Я не могу найти много документов в '// go: nosplit', где я должен это поставить? Я получаю ошибку переполнения стека ссылок, если я помещаю ее выше команд 'go func' и даже выше функции func (r * Request) Spawn'. Есть предположения? – ChrisR

+1

Это задокументировано [здесь] (https://golang.org/cmd/compile/). Я думаю, что это работает только для названных функций. Я попросил экспертную оценку этого ответа на голан-орехах. Может, кто-то придумает что-то умнее. – thwd

+0

На что вы собираетесь использовать RawSyscall? Переписывание всей сети IO, которая является областью, где уже была выполнена большая настройка производительности? Это пытается сделать HTTP-запросы, что является довольно сложным процессом, а не одним системным вызовом. – JimB