AVK Selected
Показавшиеся интересными, на мой вкус, посты
Синхронный и асинхронный IO
16.02.2014
|
scale_tone |
Здравствуйте, Аноним, Вы писали:
А>Мне кажется второй способ более простой и удобнее для чтения отладки и т.п.
А>Есть ли какая-то эффективность у 1го способа или это уже можно считать как устаревший подход ?
На нынешнем этапе развития науки и техники устаревшим считается скорее второй подход.
Во всех трех вариантах:
1) Begin/End-методы,
2) обертка Task.FromAsync над ними, упомянутая TK
и
3) синхронные сетевые вызовы в теле таски,
непосредственно вызовы и ожидания ответов действительно происходят в потоках из IO-пула. Но вариант №3 потребляет _еще_один_ поток, в дополнение к уже потребляемым IO-потокам. Т.е. в общем случае требует в два раза больше потоков (=> в два раза больше памяти) и рано или поздно упрется в лимит на их число.
Преимущества первых двух вариантов над третьим особенно хорошо видны, если принудительно ограничить размер ThreadPool-а:
Важно не забыть убрать ограничение на число параллельных коннектов к одному и тому же адресу в app.config:
Имеем закономерный результат:
Однако! При определенных условиях синхронный вызов метода сервиса в таске может оказаться выгоднее по скорости. Например, если этот метод — очень быстрый.
Поднимем сервис на локальной машине, уберем из его метода Thread.Sleep() и снимем лимит на число потоков. Имеем:
Таким образом, выяснять, какой из вариантов быстрее и эффективнее в Вашем конкретном случае, все равно придется с помощью нагрузочных тестов.
P.S. — речь здесь идет об огромном числе параллельных вызовов в нагруженных системах. Если у Вас виндовый клиент, дергающий что-то раз в час — все, что выше, можно не читать.
А>Мне кажется второй способ более простой и удобнее для чтения отладки и т.п.
А>Есть ли какая-то эффективность у 1го способа или это уже можно считать как устаревший подход ?
На нынешнем этапе развития науки и техники устаревшим считается скорее второй подход.
Во всех трех вариантах:
1) Begin/End-методы,
2) обертка Task.FromAsync над ними, упомянутая TK
и
3) синхронные сетевые вызовы в теле таски,
непосредственно вызовы и ожидания ответов действительно происходят в потоках из IO-пула. Но вариант №3 потребляет _еще_один_ поток, в дополнение к уже потребляемым IO-потокам. Т.е. в общем случае требует в два раза больше потоков (=> в два раза больше памяти) и рано или поздно упрется в лимит на их число.
Преимущества первых двух вариантов над третьим особенно хорошо видны, если принудительно ограничить размер ThreadPool-а:
class Program
{
const int CallCount = 30;
// this service method sleeps for 1 second and returns some JSON
const string LongMethodUri = "http://beginendtasktest.azurewebsites.net/api/values";
static void Main()
{
// limiting the thread pool size
ThreadPool.SetMinThreads(3, 3);
ThreadPool.SetMaxThreads(3, 3);
Measure(BeginEndTest);
Measure(TaskFromAsyncTest);
Measure(SynchronousTaskTest);
int workerThreads, ioThreads;
ThreadPool.GetAvailableThreads(out workerThreads, out ioThreads);
Console.WriteLine("Worker threads: {0}, IO threads: {1}", workerThreads, ioThreads);
Console.WriteLine("Any key press you...");
Console.ReadKey();
}
static void Measure(Action todo)
{
var sw = new Stopwatch();
sw.Start();
todo();
sw.Stop();
Console.WriteLine("{0} took {1} ms", todo.Method.Name, sw.ElapsedMilliseconds);
}
static void BeginEndTest()
{
var asyncResults = new Dictionary<WebRequest, IAsyncResult>();
for (int i = 0; i < CallCount; i++)
{
var request = WebRequest.Create(new Uri(LongMethodUri));
asyncResults[request] = request.BeginGetResponse(null, null);
}
foreach (var pair in asyncResults)
{
pair.Key.EndGetResponse(pair.Value);
}
}
static void TaskFromAsyncTest()
{
var tasks = Enumerable.Range(0, CallCount).Select(i =>
{
var request = WebRequest.Create(new Uri(LongMethodUri));
return Task.Factory.FromAsync
(
request.BeginGetResponse,
(Func<IAsyncResult, WebResponse>)request.EndGetResponse,
null
);
});
Task.WaitAll(tasks.ToArray());
}
static void SynchronousTaskTest()
{
var tasks = Enumerable.Range(0, CallCount).Select(i => Task.Run(() =>
{
using ((WebRequest.Create(new Uri(LongMethodUri))).GetResponse()) { }
}));
Task.WaitAll(tasks.ToArray());
}
}
Важно не забыть убрать ограничение на число параллельных коннектов к одному и тому же адресу в app.config:
<system.net>
<connectionManagement>
<remove address="*"/>
<add address="*" maxconnection="999999"/>
</connectionManagement>
</system.net>
Имеем закономерный результат:
BeginEndTest took 2346 ms
TaskFromAsyncTest took 2245 ms
SynchronousTaskTest took 11067 ms
Worker threads: 3, IO threads: 3
Any key press you...
Однако! При определенных условиях синхронный вызов метода сервиса в таске может оказаться выгоднее по скорости. Например, если этот метод — очень быстрый.
Поднимем сервис на локальной машине, уберем из его метода Thread.Sleep() и снимем лимит на число потоков. Имеем:
BeginEndTest took 471 ms
TaskFromAsyncTest took 626 ms
SynchronousTaskTest took 202 ms
Worker threads: 32767, IO threads: 1000
Any key press you...
Таким образом, выяснять, какой из вариантов быстрее и эффективнее в Вашем конкретном случае, все равно придется с помощью нагрузочных тестов.
P.S. — речь здесь идет об огромном числе параллельных вызовов в нагруженных системах. Если у Вас виндовый клиент, дергающий что-то раз в час — все, что выше, можно не читать.
16.02.2014 4 комментария |
_>
_>Однако! При определенных условиях синхронный вызов метода сервиса в таске может оказаться выгоднее по скорости. Например, если этот метод — очень быстрый.
_>Поднимем сервис на локальной машине, уберем из его метода Thread.Sleep() и снимем лимит на число потоков. Имеем:
_>
Мне кажется тест не совсем корректный
т.к. в случае 3. синхронный метод скачивает все данные, в асинхронных вариантах первый callback ни о чем не говорит, там может быть принят только 1 байт.
Обычно в обработчиках запускается повторно BeginReceive пока не будут получены все данные, вот этого в тестах нет, поэтому вероятно тест с удаленным сервером дал такой более быстрый результат.
А>Мне кажется тест не совсем корректный
А>т.к. в случае 3. синхронный метод скачивает все данные, в асинхронных вариантах первый callback ни о чем не говорит, там может быть принят только 1 байт.
А>Обычно в обработчиках запускается повторно BeginReceive пока не будут получены все данные, вот этого в тестах нет, поэтому вероятно тест с удаленным сервером дал такой более быстрый результат.
Код вызываемого метода:
Метод даже 1 байт выдает не сразу, а через секунду.
Впрочем, вставьте вычитывание всего респонса сами и проверьте.
_>Здравствуйте, Аноним, Вы писали:
А>>Мне кажется тест не совсем корректный
А>>т.к. в случае 3. синхронный метод скачивает все данные, в асинхронных вариантах первый callback ни о чем не говорит, там может быть принят только 1 байт.
А>>Обычно в обработчиках запускается повторно BeginReceive пока не будут получены все данные, вот этого в тестах нет, поэтому вероятно тест с удаленным сервером дал такой более быстрый результат.
_>Код вызываемого метода:
_>
_>Метод даже 1 байт выдает не сразу, а через секунду.
_>Впрочем, вставьте вычитывание всего респонса сами и проверьте.
Ну так это ваш локальный, на нем собственно разницы и не видно, точнее синхронный даже в плюсе.
А на удаленном сервере такой же код ?
А>Ну так это ваш локальный, на нем собственно разницы и не видно, точнее синхронный даже в плюсе.
А>А на удаленном сервере такой же код ?
Еще раз.
По адресу http://beginendtasktest.azurewebsites.net/api/values сейчас доступен метод, код которого следующий:
Этот код задеплоил туда я. Специально чтобы смоделировать долгий сетевой вызов.
То, что метод выполняется секунду, хорошо видно:
Множество параллельных синхронных вызовов этого метода в теле таски работает медленнее и хуже, чем такое же множество параллельных асинхронных вызовов.
Множество параллельных синхронных вызовов другого, более быстрого метода в теле таски иногда может работать быстрее, чем такое же множество параллельных асинхронных вызовов.