2016-12-30 7 views
4

Я собрал небольшой тестовый жгут для диагностики того, почему пропускная способность моего приложения обработки данных C# (его основная функция выбирает записи в партиях 100 с удаленного сервера базы данных с использованием неблокирующего IO и выполняет простую обработку на них) ниже, чем это могло бы быть. Я заметил, что во время работы приложение не сталкивается с узкими местами на пути CPU (< 3%), сетевым или дисковым IO или оперативной памятью и не подчеркивает сервер базы данных (набор данных в базе данных почти всегда полностью ОЗУ). Если я запускаю несколько экземпляров приложения параллельно, я могу получить до ~ 45 экземпляров с только 10% -ной деградацией задержек, но с 45-кратным увеличением пропускной способности, прежде чем загрузка процессора на сервере базы данных станет узким местом (на тот момент, там до сих пор нет узких мест в ресурсах на клиенте).Почему пропускная способность этого приложения для обработки данных C# намного ниже, чем необработанные возможности сервера?

Мой вопрос: почему TPL не увеличивает количество задач в полете или иным образом увеличивает пропускную способность, когда клиентский сервер способен существенно повысить пропускную способность?

Упрощенный фрагмент кода:

public static async Task ProcessRecordsAsync() 
    { 
     int max = 10000; 
     var s = new Stopwatch(); 
     s.Start(); 
     Parallel.For(0, max, async x => 
     { 
      await ProcessFunc(); 
     }); 
     s.Stop(); 
     Console.WriteLine("{2} Selects completed in {0} ms ({1} per ms).", s.ElapsedMilliseconds, ((float)s.ElapsedMilliseconds)/max, max); 
    } 

    public static async Task ProcessFunc() 
    { 
     string sql = "select top 100 MyTestColumn from MyTestTable order by MyTestColumn desc;"; 
     string connStr = "<blah>..."; 

     using (SqlConnection conn = new SqlConnection(connStr)) 
     { 
      try 
      { 
       conn.Open(); 
       SqlCommand cmd = new SqlCommand(sql, conn); 
       DbDataReader rdr = await cmd.ExecuteReaderAsync(); 

       while (rdr.Read()) 
       { 
        // do simple processing here 
       } 
       rdr.Close(); 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine(ex.ToString()); 
      } 
     } 
    } 
+0

Запустите профайлер с кодом, чтобы увидеть код, который использует больше всего времени, а затем определите, следует ли вам оптимизировать этот код. –

+1

@RudyTheHunter, это неблокирующие вызовы базы данных, которые занимают больше всего времени, но на клиентском компьютере практически не нужны ресурсы и должны легко распараллеливаться, что подтверждается тем фактом, что я могу запускать 45 экземпляров приложения, не сталкиваясь с какими-либо узкими местами ресурсов на клиент. Я пытаюсь понять, почему я не могу получить такую ​​же степень распараллеливания в одном процессе. – Dan

+1

Кажется, что каждый раз выбирает один и тот же самый 100 строк из базы данных. Мне интересно, если вы снова и снова сталкиваетесь с теми же 100 рядами. Вы сказали, что делаете неблокирующие вызовы базы данных, но делаете им простые обновления. Вам нужно выбрать более 100 строк в качестве лучшего примера? Я думаю, вам нужно поделиться более подробными сведениями о том, что происходит в этом процессе. –

ответ

4

Parallel For не пытается задушить жизнь вашего процессора и максимизировать количество одновременных потоков, работающих на вас. Он использует количество ядер в качестве отправной точки и может увеличиваться в зависимости от характера рабочей нагрузки. См. this question.

Как это происходит, вы на самом деле do имеют блокировку IO ... при открытии строк подключения и чтения. Вы можете попробовать это вместо этого:

//.... 
using (var conn = new SqlConnection(connStr)) 
{ 
    await conn.OpenAsync(); 
    SqlCommand cmd = new SqlCommand(sql, conn); 
    try 
    { 
    using (var rdr = await cmd.ExecuteReaderAsync()) 
    { 
     while (await rdr.ReadAsync()) 
     { 
     // do simple processing here 
     } 
    } 
    } 
    catch (Exception ex) 
    { 
    Console.WriteLine(ex.ToString()); 
    } 
} 
//... 
+2

Спасибо за ссылку на другой вопрос. Я преобразовал дополнительные вызовы, которые вы отметили, для асинхронности, что привело к простому увеличению пропускной способности 1000 раз, что, несомненно, стало основной проблемой. Я небрежно полагал, что 'conn.openAsync()' не имеет большого значения из-за пула соединений, а 'rdr.ReadAsync()' не имеет большого значения из-за буферизации, которая возникает при чтении наборов результатов. Очевидно, я был неправ. – Dan

+0

Да, я наткнулся на это самое предположение - легко сделать - рад, что у вас есть выигрыш в перфомансе, который вы искали. – Clay

1

Ваш пример может быть ограничено быть максимальным числом pooled SQL Connections в приложении, которое является 100 по умолчанию. Это может объяснить, почему вы получаете большую пропускную способность при запуске нескольких экземпляров приложения. Вы можете попытаться контролировать количество подключений на SQL-сервере, чтобы убедиться, что это так.

+0

После преобразования всех вызовов async в качестве предлагаемого @Clay я иногда нажимаю ограничение пула соединений, поэтому увеличение его, как вы предлагаете, позволит мне получить дополнительную пропускную способность. – Dan