Что может быть лучше для асинхронного выполнения метода, чем BlockingCollection?
Я написал такой пример, чтобы измерить, насколько быстро BlockingCollection для асинхронного выполнения
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace TestBlockingCollection
{
class Program
{
static void Main(string[] args)
{
BlockingCollection<int> blockingCollection = new BlockingCollection<int>();
Stopwatch sw = Stopwatch.StartNew();
Task.Factory.StartNew(() =>
{
int i = 0;
while (true)
{
Console.WriteLine("Adding " + i);
sw = Stopwatch.StartNew();
blockingCollection.Add(i++);
Thread.Sleep(1000);
}
});
Task.Factory.StartNew(() =>
{
while (true)
{
int i = blockingCollection.Take();
sw.Stop();
long microseconds = sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
Console.WriteLine("Received " + i + ". Spent " + microseconds + " microseconds.");
}
});
while(true)
{
Thread.Sleep(1000);
}
}
}
}
Результаты разочаровывают:
Adding 0
Received 0. Spent 19593 microseconds.
Adding 1
Received 1. Spent 220 microseconds.
Adding 2
Received 2. Spent 38 microseconds.
Adding 3
Received 3. Spent 104 microseconds.
Adding 4
Received 4. Spent 46 microseconds.
Adding 5
Received 5. Spent 37 microseconds.
Adding 6
Received 6. Spent 112 microseconds.
Adding 7
Received 7. Spent 103 microseconds.
Adding 8
Received 8. Spent 104 microseconds.
Adding 9
Received 9. Spent 384 microseconds.
Adding 10
Received 10. Spent 102 microseconds.
Adding 11
Received 11. Spent 39 microseconds.
Adding 12
Received 12. Spent 51 microseconds.
Adding 13
Received 13. Spent 42 microseconds.
Adding 14
Received 14. Spent 40 microseconds.
Adding 15
Received 15. Spent 40 microseconds.
Adding 16
Received 16. Spent 42 microseconds.
Adding 17
Received 17. Spent 40 microseconds.
Adding 18
Received 18. Spent 41 microseconds.
Adding 19
Received 19. Spent 42 microseconds.
Adding 20
Received 20. Spent 62 microseconds.
Adding 21
Received 21. Spent 36 microseconds.
Adding 22
Received 22. Spent 39 microseconds.
Adding 23
Received 23. Spent 35 microseconds.
Adding 24
Received 24. Spent 40 microseconds.
Adding 25
Received 25. Spent 63 microseconds.
Adding 26
Received 26. Spent 56 microseconds.
Adding 27
Received 27. Spent 42 microseconds.
Adding 28
Received 28. Spent 41 microseconds.
Adding 29
Received 29. Spent 42 microseconds.
Adding 30
Received 30. Spent 41 microseconds.
Adding 31
Received 31. Spent 651 microseconds.
Adding 32
Received 32. Spent 43 microseconds.
Adding 33
Received 33. Spent 58 microseconds.
Adding 34
Received 34. Spent 43 microseconds.
Adding 35
Received 35. Spent 41 microseconds.
Adding 36
Received 36. Spent 59 microseconds.
Adding 37
Received 37. Spent 38 microseconds.
Adding 38
Received 38. Spent 38 microseconds.
Adding 39
Received 39. Spent 38 microseconds.
Adding 40
Received 40. Spent 42 microseconds.
Adding 41
Received 41. Spent 59 microseconds.
Adding 42
Received 42. Spent 40 microseconds.
Adding 43
Received 43. Spent 42 microseconds.
Adding 44
Received 44. Spent 41 microseconds.
Adding 45
Received 45. Spent 39 microseconds.
Adding 46
Received 46. Spent 42 microseconds.
Adding 47
Received 47. Spent 41 microseconds.
Adding 48
Received 48. Spent 41 microseconds.
Adding 49
Received 49. Spent 42 microseconds.
Adding 50
Received 50. Spent 35 microseconds.
Adding 51
Received 51. Spent 42 microseconds.
Adding 52
Received 52. Spent 39 microseconds.
Adding 53
Received 53. Spent 43 microseconds.
Adding 54
Received 54. Spent 35 microseconds.
Adding 55
Received 55. Spent 60 microseconds.
Adding 56
Received 56. Spent 59 microseconds.
Adding 57
Received 57. Spent 55 microseconds.
Adding 58
Received 58. Spent 74 microseconds.
Adding 59
Received 59. Spent 56 microseconds.
Adding 60
Received 60. Spent 42 microseconds.
В среднем я тратил около 50 микросекунд, но иногда я тратил до 600 микросекунд!
Даже используя мой медленный Pentium U5400, я ожидаю, что он должен быть постоянным в несколько, не более 10 и никогда не более 10 мкс.
Какой более быстрый метод.NET имеет для async exec? После запланированного async exec мне нужно запустить его как можно скорее. Это финансовые расчеты, чувствительные ко времени.
Блокировка коллекции гарантирует порядок и гарантирует, что элементы будут обрабатываться один за другим, поэтому этот вопрос содержит фактически два вопроса
- Есть ли у нас что-то быстрее, если мне НУЖЕН заказ, и мне НУЖНЫ элементы, которые будут обработаны в порядке их появления? Т.е. мне нужен запрос FIFO.
- У нас есть что-то быстрее, если я не забочусь о заказе, и мне все равно, обрабатываются ли товары по одному или параллельно?
Я думаю, что ответы:
Нет. Я должен использовать BlockingCollection для этой ссылки. Хорошо ли использовать BlockingCollection
в качестве FIFO-запроса с одним производителем и с одним потребителем? Я могу попробовать делегатов? http://msdn.microsoft.com/en-us/library/2e08f6yc.aspx
2 ответа
После нескольких часов экспериментов я думаю, что мешает измерение; Я изменил его для запуска на 10000 и результаты:
Итого 214,0 за 100000 итераций, в среднем 0,00214 мс
Код в значительной степени такой же, как у вас (он ниже для справки); Я сделал сборку релиза
Я экспериментировал с использованием Барьера, но это было медленнее. Я также пытался сделать это, используя только блокировки, но не смог заставить это работать.
static void Main(string[] args)
{
var barrier = new Barrier(2);
var collection = new List<int>();
var num_iterations = 100000;
var iterations = num_iterations;
var total_time_ms = 0.0M;
Stopwatch sw = new Stopwatch();
total_time_ms = 0.0M;
iterations = num_iterations;
var blockingCollection = new BlockingCollection<int>();
Task.Factory.StartNew(() =>
{
int i = 0;
while (iterations-- > 0)
{
sw.Restart();
blockingCollection.Add(i++);
}
});
Task.Factory.StartNew(() =>
{
var expected_value = 0;
while (iterations > 0) // stop when performed certain number
{
int i = blockingCollection.Take();
sw.Stop();
long microseconds = sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
total_time_ms += microseconds;
if (i != expected_value)
Console.WriteLine(String.Format("** expected {0} got {1}", i, expected_value));
expected_value++;
}
});
while (iterations > 0)
{
Thread.Sleep(1000);
}
Console.WriteLine(String.Format("Total {0} for {1} iterations, average {2}", total_time_ms, num_iterations, total_time_ms / num_iterations));
Я считаю, что BlockingCollection - хорошая коллекция для этого. Есть и другие способы сделать это, но это сложная область, и шансы получить что-то быстрее, чем это, маловероятны.
Во-первых, если вы действительно заботитесь о микросекундах, возможно, вам не следует использовать.Net (потому что его сборщик мусора может вызвать непредсказуемые задержки) или Windows (потому что это не ОС реального времени).
Чтобы на самом деле ответить на ваши вопросы:
Я думаю, что 50 микросекунд это действительно небольшое количество времени и
BlockingCollection<T>
высоко оптимизирован, поэтому я не думаю, что вы сможете сделать это намного быстрее. Возможно, вы сможете сделать это с помощью спин-блокировок, за счет потери процессорного времени, ничего не делая.Вызов делегатов асинхронно почти такой же, как использование
ThreadPool.QueueUserWorkItem()
, Так что, да, это, вероятно, близко к лучшему из того, что вы можете получить. Хотя синхронные вызовы всегда будут быстрее.