Текущая задача, перебирающая массивные словари, вызывает у меня головную боль. Я не могу точно определить точный источник высокой загрузки ЦП, поэтому надеюсь, что некоторые гуру C # здесь могут дать мне несколько советов и подсказок.
Установка состоит из 10 предварительно выделенных словарей Guid-byte [], каждый из которых содержит миллион записей. Процесс перебирает все из них, каждый словарь имеет свой собственный поток. Простая итерация по всем из них и передача ссылки byte [] на делегат итерации, приводящий к случайному результату, занимает менее 2 мс, но фактический доступ к любому байту в содержащихся записях приводит к увеличению этого числа до 300 + мс.
Примечание: делегат итерации создается перед любыми итерациями, и тогда я только передаю ссылку.
Если я ничего не делаю с полученной байтовой ссылкой, все это невероятно быстро:
var iterationDelegate = new Action<byte[]>((bytes) =>
{
var x = 5 + 10;
});
Но как только я пытаюсь получить доступ к самому первому байту (который фактически содержит указатель на метаданные строки где-то еще)
var iterationDelegate = new Action<byte[]>((bytes) =>
{
var b = (int)bytes[0];
});
Общее время увеличивается, и что еще более странно, первый набор итераций занимает 30 мс, второй 40+, третий 100+ и четвертый может занять 500 мс + ... затем я прекращаю тестировать производительность, Отключаю вызывающий поток в течение нескольких секунд, и как только я начинаю повторять итерацию, она начинается случайно через 30 мс, а затем поднимается так же, как и раньше, пока я снова не дам ей «время дышать».
Когда я смотрю его в дереве вызовов VS-процессора, 93% ЦП потребляется [External Code], который я не могу просмотреть или хотя бы увидеть, что это такое.
Могу ли я чем-нибудь помочь? У ГК тяжелые времена?
Редактировать 1: Фактический код, который я хочу запустить:
var iterationDelegate = new Action<byte[]>((data) =>
{
//compare two bytes, ensure the row belongs to desired table
if (data[0] != table.TableIndex)
return;
//get header length
var headerLength = (int)data[1];
//process the header info and retrieve the desired column data position:
var columnInfoPos = (key * 6) + 2;
var pointers = new int[3] {
//data position
BitConverter.ToInt32(new byte[4] {
data[columnInfoPos],
data[columnInfoPos + 1],
data[columnInfoPos + 2],
data[columnInfoPos + 3] }),
//data length
BitConverter.ToUInt16(new byte[2] {
data[columnInfoPos + 4],
data[columnInfoPos + 5] }),
//column info position
columnInfoPos };
});
Но этот код еще медленнее, время итерации ~ 150, ~ 300, ~ 600, 700 +
Это рабочий класс, который поддерживается для каждого магазина в соответствующих потоках:
class PartitionWorker
{
private ManualResetEvent waitHandle = new ManualResetEvent(true);
private object key = new object();
private bool stop = false;
private List<Action> queue = new List<Action>();
public void AddTask(Action task)
{
lock (key)
queue.Add(task);
waitHandle.Set();
}
public void Run()
{
while (!stop)
{
lock (key)
if (queue.Count > 0)
{
var task = queue[0];
task();
queue.Remove(task);
continue;
}
waitHandle.Reset();
waitHandle.WaitOne();
}
}
public void Stop()
{
stop = true;
}
}
И, наконец, код, который запускает итерации, этот код запускается из Задачи для каждого входящего запроса TCP.
for (var memoryPartition = 0; memoryPartition < partitions; memoryPartition++)
{
var memIndex = memoryPartition;
mem[memIndex].AddJob(() =>
{
try
{
//... to keep it shor i have excluded readlock and try/finally
foreach (var obj in mem[memIndex].innerCache.Values)
{
iterationDelegate(obj.bytes);
}
//release readlock in finally..
}
catch
{
}
finally
{
latch.Signal();
}
});
}
try
{
latch.Wait(50);
sw.Stop();
Console.WriteLine("Found " + result.Count + " in " + sw.Elapsed.TotalMilliseconds + "ms");
}
catch
{
Console.WriteLine(">50");
}
Edit2:
Словари предварительно распределяются с помощью
private Dictionary<Guid, byte[]> innerCache = new Dictionary<Guid, byte[]>(part_max_entries);
и в отношении записей они составляют в среднем 70 байт. Процесс занимает около 2 ГБ памяти с 10 000 000 записей, разделенных на 10 словарей.
Структура записи следующая:
T | HL | {POS | POS | POS | POS | ЛЕН | LEN} | {байты данных}
где | указывает отдельные байты
- T - байтовый указатель на словарь метаданных таблицы
- HL - длина байта части заголовка, если запись
POS и LEN повторяются для каждого значения данных в записи:
- POSx4 = int, указывающий положение этих данных в записи
- POSx2 = короткая длина этих данных в записи
, а затем {байты данных} являются полезными данными