2022-04-17 19:29:22
#codaza_отвечает
Что же... и в этот раз, коллективными усилиями, предложенный фрагмент кода, без преувеличения, был разобран по косточкам. В нашем сообществе не мало действительно профессиональных разработчиков. Если вы не успели пробежаться по комментариям в предыдущем посте, то прошу вас не полениться и сделать это, так как вы увидите развитие мысли и ссылки на дополнительную информацию от участников группы.
Где же был зарыт "главный слон" ? Проблема, с которой данный код никак нельзя было допускать в production-среду. Проблема была на строках 12-13:
int keyWordIndex = dataSlice.Value.TextFraction.Substring(actualPosition).IndexOf(keyWord);
Вызов метода
Substring() - вот основная проблема. Дело в том, что при каждом вызове этого метода, в памяти будет создаваться
полная копия строки, содержащаяся в свойстве
TextFraction. Это не проблема при нечастных вызовах на небольших строках и без использования циклов. Для нашей ситуации это совершенно не годится, так как методу предстоит работать с большим массивом данных. Только представьте что может произойти с оперативной памятью при частых вызовах метода
ExtractStochasticDataAsync(). И первым проблему заметил Александр Максименко, указав на то что вместо вызова
Substring() следует делать вызов метода расширения для класса
String - AsSpan(). Такой подход позволит не создавать копию строки
TextFraction в оперативной памяти, что существенно снизит нагрузку на неё.
Далее, Александр увидел возможность для параллельной обработки данных в словаре
dataSlices. При параллельной обработке нужно учитывать проблемы совместного доступа, поэтому, для словаря
indexes, вместо
Dictionary, Александр взял потокобезопасную коллекцию
ConcurrentDictionary.
Метод
ExtractStochasticDataAsync() является асинхронным и хорошей практикой является предоставления возможности для остановки метода (тем более метод работает в цикле с большим объемом данных). На эту проблему обратил внимание Aleksandr Radchenko, предложив использование
CancellationToken.
Итоговый вариант после рефакторинга стал выглядеть так:
public async Task
> ExtractStochasticDataAsync(string keyWord, CancellationToken ct = default)
{
IDictionary dataSlices = await LoadAnalyticalDataSlicesAsync(ct);
ConcurrentDictionary indexes = new();
var tasks = dataSlices.Select(async ds =>
{
ct.ThrowIfCancellationRequested();
int actualPosition = await LoadActualPositionAsync(ct);
int keyWordIndex = ds.Value.TextFraction
.AsSpan(actualPosition).IndexOf(keyWord);
indexes.TryAdd(ds.Key, keyWordIndex + actualPosition);
});
await Task.WhenAll(tasks);
return indexes;
}
Кроме того, Aleksandr Radchenko предложил еще ряд важных улучшений с примерами листингов (за что ему отдельное спасибо). Например, использовать в качестве возвращаемого значения метода ExtractStochasticDataAsync() асинхронную реализацию интерфейса IAsyncEnumerable и оператор yield, что позволит организовать асинхронную обработку данных из вызываемого метода. Это потребует изменение выходного API у метода ExtractStochasticDataAsync(), но если это возможно по имеющимся бизнес-требованиям, сделать это точно стоит. Этот вариант будет выглядеть так:
public async IAsyncEnumerable<(int Key, int Position)> ExtractStochasticDataAsync01(string keyWord, [EnumeratorCancellation] CancellationToken ct = default)
{
IDictionary dataSlices = await LoadAnalyticalDataSlicesAsync(ct);
foreach(var dataSlice in dataSlices)
{
int actualPosition = await LoadActualPositionAsync(ct);
int keyWordIndex = dataSlice.Value.TextFraction
.AsSpan(actualPosition).IndexOf(keyWord);
ct.ThrowIfCancellationRequested();
yield return (dataSlice.Key, keyWordIndex + actualPosition);
};
}
Из таких code review можно получить гораздо больше ценных и практических знаний нежели из очередной статьи в интернете.
Всем активным участникам огромное спасибо за вклад!
775 views16:29