Оптимизация. Пул массивов

При разработке скриптов приходится работать с большими массивами данных. Создание и удаление больших массивов трудозатратный процесс. Если в скрипте часто создаются большие массивы, то загрузка процессора может просесть, что сказывается на времени оптимизации.

Чтобы этого избежать, можно использовать внутренний пул массивов в TSLab. Это работает только для оптимизации.

Пул массивов - это коллекция массивов, которые находятся в памяти. Например, запускаем оптимизацию скрипта. В скрипте запрашиваем новый массив из пула. Пул ищет у себя подходящий свободный массив, по типу и длине данных. Если такого массива в пуле нет, то он создается. Далее скрипт помещает в этот массив свои данные, производит вычисления, выводит их на график если надо. После выполнения скрипта контекст данных освобождается, и все массивы из пула, которые были использованы в этом контексте, очищаются и помещаются обратно в пул. Далее, при проходе следующего итерации оптимизации, скрипт запрашивает массив из пула и действия повторяются. Таким образом, сокращается количество создаваемых массивов.

При освобождении контекста массив будет помещаться обратно в пул, только если он не закеширован.

Методы:

T[] IContext.GetArray<T>(int count) – получить массив с типом T и количеством элементов count из пула. Если в пуле нет такого массива, то он создается.

void ReleaseArray(Array array) – передать обратно массив в пул. При этом массив очищается. Метод ReleaseArray вызывается автоматически при освобождении контекста (IContext), после прохода оптимизации (если массив не был помещен в кеш).

Рассмотрим пример. Создадим массив с double числами, поместим в него среднюю цену бара и в конце выведем в лог хеш-код этого объекта.

public class TestArrayPool : IExternalScript
{
	public IntOptimProperty Param = new IntOptimProperty(1, 1, 5, 1);
	public void Execute(IContext ctx, ISecurity sec)
	{
		var bars = sec.Bars;
		var arr = new double[bars.Count];
		for (int i = 0; i < bars.Count; i++)
			arr[i] = (bars[i].High + bars[i].Low) / 2;
		ctx.Log($"hash: {arr.GetHashCode()}", MessageType.Info, true);
	}
}

Теперь проведем оптимизацию, только в настройках TSLab поставим 'Количество потоков оптимизации' равное 1. Это нужно чтобы проверить как часто у нас создается новый массив. Так как указали количество потоков 1, то сначала пройдет один проход итерации, потом второй и так далее, друг за другом. В логе мы видим разные значения хеш-кодов. Это означает, что при каждом проходе оптимизации создавался новый массив.

Теперь мы немного изменим наш пример, вместо создания массива будем получать его из пула: var arr = ctx.GetArray<double>(bars.Count);

public class TestArrayPool : IExternalScript
{
	public IntOptimProperty Param = new IntOptimProperty(1, 1, 5, 1);
	public void Execute(IContext ctx, ISecurity sec)
	{
		var bars = sec.Bars;
		var arr = ctx.GetArray<double>(bars.Count);
		for (int i = 0; i < bars.Count; i++)
			arr[i] = (bars[i].High + bars[i].Low) / 2;
		ctx.Log($"hash: {arr.GetHashCode()}", MessageType.Info, true);
	}
}

Запускаем оптимизацию, снова с одним потоком. В логе мы видим одинаковый хеш-код. Это означает что во всех итерациях оптимизации использовался только один массив. Мы брали его из пула, использовали, при завершении итерации он помещался обратно в пул. При следующей итерации мы снова берем этот же массив из пула.

Что будет если в настройках поставить максимальное количество потоков и запустить оптимизацию на 10000 проходов? Оптимизация запустится на всех имеющихся потоках процессора. При каждой итерации будет браться массив из пула, обрабатываться и помещаться обратно в пул. Но так как потоков теперь у нас много, то теперь одного массива в пуле на всех не хватит. В этом случае будут созданы дополнительные массивы в пуле. И в конце оптимизации окажется что количество массивов в пуле равно количеству потоков. Хотя проходов у нас было 10000.

Во многих стандартных блоках TSLab используется пул массивов. Например блок расчета ATR:

public static IList<double> AverageTrueRangeNew(IReadOnlyList<IDataBar> candles, int period, IMemoryContext context)
{
	var listTr = Series.TrueRange(candles, context);
	var res = Series.SMA(listTr, period);
	context?.ReleaseArray((Array)listTr);
	return res;
}

Заметка: Пул потоков в TSLab это аналог ArrayPool из .NET Core.

Last updated