Есть определённые пользовательские выражения с тривиальными возможностями (скобки, вызовы функций, доступ к переменным), есть парсер — парсит строку в AST,
есть простой интерпретатор, ему на вход AST и enviroment, внутри обход AST и для каждого узла выполняется соотвествующее ему действия.
Стоит задача разогнать скорость выполнения, тк при большом кол-ве повторений скорости интерпретатора уже недостаточно.
Какие подходы на мой взгляд возможны:
1. по AST сформировать функцию-делегат, которая является композицией используемых операций. до конца ещё не понятно как сделать, но где-то слышал про технику.
2. собрать лямбда выражение и компильнуть его.
тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений.
также слышал, что компиляция лямбд сильно не быстрый процесс и при частом выполнении разных выражений может стать проблемой..
3. заморочится за прям генерацию MSIL, но это видится сильно сложнее чем 2, и есть ощущение что не будет сильно быстрее..
Кто сталкивался с подобными задачами или имеет понимание, какой подход лучше выбрать и почему?
MH>2. собрать лямбда выражение и компильнуть его. MH>тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений.
Может быть, выяснить? Обложить юзингами. Попрофилировать.
MH>также слышал, что компиляция лямбд сильно не быстрый процесс и при частом выполнении разных выражений может стать проблемой..
Я использовал для аналогичных целей IronPython. Время, затрачиваемое на интерпретирацию каждого выражения измерялось миллисекундами, ни как не меньше.
Кэшировать не получится? Часто ли меняются выражения?
Здравствуйте, MadHuman, Вы писали:
MH>2. собрать лямбда выражение и компильнуть его. MH>тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений. MH>также слышал, что компиляция лямбд сильно не быстрый процесс и при частом выполнении разных выражений может стать проблемой..
Из перечисленного однозначно 2. Скорость сравнима с рукописным кодом. Расход памяти следует контролировать кешированием.
Ещё один вариант — генерировать C# код и компилировать его в runtime. Гуглить по compiler as a service.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, MadHuman, Вы писали:
MH>Есть определённые пользовательские выражения с тривиальными возможностями (скобки, вызовы функций, доступ к переменным), есть парсер — парсит строку в AST, MH>есть простой интерпретатор, ему на вход AST и enviroment, внутри обход AST и для каждого узла выполняется соотвествующее ему действия. MH>Стоит задача разогнать скорость выполнения, тк при большом кол-ве повторений скорости интерпретатора уже недостаточно.
MH>Какие подходы на мой взгляд возможны: MH>1. по AST сформировать функцию-делегат, которая является композицией используемых операций. до конца ещё не понятно как сделать, но где-то слышал про технику.
MH>2. собрать лямбда выражение и компильнуть его. MH>тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений. MH>также слышал, что компиляция лямбд сильно не быстрый процесс и при частом выполнении разных выражений может стать проблемой..
MH>3. заморочится за прям генерацию MSIL, но это видится сильно сложнее чем 2, и есть ощущение что не будет сильно быстрее.. https://www.codeproject.com/Tips/715891/Compiling-Csharp-Code-at-Runtime
4. sql сервер
MH>Кто сталкивался с подобными задачами или имеет понимание, какой подход лучше выбрать и почему?
Сильно зависит от типа вычислений и данных, а не от выражений.
Всё от поставленной задачи зависит. (Может достаточно будет на 100500 потоков распаралелить)
MH>Кто сталкивался с подобными задачами или имеет понимание, какой подход лучше выбрать и почему?
Я писал что-то похожее.
Тоже по пользовательской строке с выражением и контексту нужно было возвращать true/false.
Делал через Expressions с компиляцией в лямбду.
Внешний интерфейс был примерно таким:
Func<T, bool> AST.Compile<T>(string expression, T context);
Поверх этого — кэш по связке expression+T.
У меня было всего несколько возможных типов контекста и expressions менялись не часто, так что кэшем вопрос памяти закрылся.
Поспрашивай гугл по ключевым словам "C# rules engine", наверняка или готовая штука найдется или что-то похожее с открытым кодом, чтобы взять за основу.
MH>Какие подходы на мой взгляд возможны:
4. Сгенерировать бинарный код для целевого CPU (вот прямо команды по битам, как в Developers Manual)
5. Сгенерировать код для GPU
Здравствуйте, Эйнсток Файр, Вы писали:
MH>>Какие подходы на мой взгляд возможны: ЭФ>4. Сгенерировать бинарный код для целевого CPU (вот прямо команды по битам, как в Developers Manual) ЭФ>5. Сгенерировать код для GPU
6. Синтезировать вентили для FPGA
-Долго ждёте?
-Вообще-то уже две минуты, ты мог бы и побыстрее.
-Извините, больше такое не повторится. Я, наверное, слишком долго ел, и медленно одевался, простите.
-И бежал не так быстро.
-И бежал не так быстро. — Согласился с упрёком я.
Здравствуйте, MadHuman, Вы писали:
MH>2. собрать лямбда выражение и компильнуть его. MH>тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений. MH>также слышал, что компиляция лямбд сильно не быстрый процесс и при частом выполнении разных выражений может стать проблемой..
MH>3. заморочится за прям генерацию MSIL, но это видится сильно сложнее чем 2, и есть ощущение что не будет сильно быстрее..
MH>Кто сталкивался с подобными задачами или имеет понимание, какой подход лучше выбрать и почему?
1. Собрать лямбду
2. Сделать кэш вида исходная-строка=>компилированная-лямбда
3. К кэше использовать ConditionalWeakTable и больше не переживать о памяти.
Здравствуйте, alexanderfedin, Вы писали:
MH>>тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений.
A>2. Сделать кэш вида исходная-строка=>компилированная-лямбда A>3. К кэше использовать ConditionalWeakTable и больше не переживать о памяти.
кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере
всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений.
Здравствуйте, MadHuman, Вы писали:
MH>кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере MH>всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений.
А вы их в отдельный домен складывайте и потом выгружайте домен со всеми его ин-мемори ассемблями.
Здравствуйте, kov_serg, Вы писали:
MH>>кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере MH>>всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений. _>А вы их в отдельный домен складывайте и потом выгружайте домен со всеми его ин-мемори ассемблями.
дак кросс-доменные вызовы маршалинга потребуют, перфоманс хуже. за перфоманс же и боремся
Здравствуйте, MadHuman, Вы писали:
MH>дак кросс-доменные вызовы маршалинга потребуют, перфоманс хуже. за перфоманс же и боремся
Что мешает все вычисления в отдельном домене проводить?
А пример решаемой проблемы можно привести? (Объёмы и организация данных и количество проводимых вычислений).
Какой перфоманс требуется и какой достигнут? ( данных/сек, выч/сек )
Здравствуйте, kov_serg, Вы писали:
MH>>дак кросс-доменные вызовы маршалинга потребуют, перфоманс хуже. за перфоманс же и боремся _>Что мешает все вычисления в отдельном домене проводить?
ну, если задаться такой целью, то наверно это решаемо. пока неясно сколько будет трудностей, и прежде надо получить подтверждение/опровержение гипотезы которая на этот путь ведёт (что эти ассембли не выгружаются).
_>А пример решаемой проблемы можно привести? (Объёмы и организация данных и количество проводимых вычислений).
это обычно кастомные фильтры по исходным данным в которых используются функции не ложащиеся на sql, что вынуждает фильтрацию проводить на клиенте.
_>Какой перфоманс требуется и какой достигнут? ( данных/сек, выч/сек )
проблемы (время фильтрации десятки секунд) появляются при обработке порядка миллионов записей. значительная часть времени сейчас именно работа фильтров.
хотелось бы секунды. по предварительным оценкам разогнать на порядок (а может и два) возможно.
Здравствуйте, MadHuman, Вы писали:
_>>А пример решаемой проблемы можно привести? (Объёмы и организация данных и количество проводимых вычислений). MH>это обычно кастомные фильтры по исходным данным в которых используются функции не ложащиеся на sql, что вынуждает фильтрацию проводить на клиенте.
Надо точно или быстро? Может имеет смысл использовать выборки или интегральные величины или спец индексы? Все данные висят в озу или раз неложаться в sql то выниаются из базы?
MH>проблемы (время фильтрации десятки секунд) появляются при обработке порядка миллионов записей. значительная часть времени сейчас именно работа фильтров. MH>хотелось бы секунды. по предварительным оценкам разогнать на порядок (а может и два) возможно.
Каков размер одной записи с которой работает фильтр по сравнению с 4Кб.
Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду.
MH>>это обычно кастомные фильтры по исходным данным в которых используются функции не ложащиеся на sql, что вынуждает фильтрацию проводить на клиенте. _>Надо точно или быстро?
надо быстро и точно) _>Может имеет смысл использовать выборки или интегральные величины или спец индексы?
там где это становится совсем критично, строим спец индексы.
но тк это кастомные фильтры, постоянно меняются, разнообразие достаточно велико, выявлять проблемные места в таких условиях непросто.
также это могут быть и просто какие-то временные фильтры, которые пару раз будут использованы.
Вообщем задача разгона вычислений фильтров всё равно актуальна, также это снизит CPU нагрузку на сервера, что актуально.
_>Все данные висят в озу или раз неложаться в sql то выниаются из базы?
вынимаются из базы, но вынимается узкая выборка (только необходимые для дальнейшего использования поля), что происходит довольно быстро.
_>Каков размер одной записи с которой работает фильтр по сравнению с 4Кб.
существенно меньше, на модельных примерах это было несколько идшек и числовых данных (long).
_>Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду.
Ваш пример тоже встречается) сходу вспомню ещё что-то типа с датами: кол-во рабочих дней между заданными датами с учетом производственного календаря и графика работ.
есть ещё много примеров, но сейчас не вспомню. Часть из кэйсов наверно можно загнать чтоб в sql делались, но не все и с учетом того что выборка даже всего объёма быстро, время работы рабочей функции — быстро,
заметная часть времени из общей работы тратиться именно на прогон через интерпретатор — он слабое место.
Здравствуйте, MadHuman, Вы писали: MH>вынимаются из базы, но вынимается узкая выборка (только необходимые для дальнейшего использования поля), что происходит довольно быстро. _>>Каков размер одной записи с которой работает фильтр по сравнению с 4Кб. MH>существенно меньше, на модельных примерах это было несколько идшек и числовых данных (long). _>>Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду. MH>Ваш пример тоже встречается) сходу вспомню ещё что-то типа с датами: кол-во рабочих дней между заданными датами с учетом производственного календаря и графика работ.
Это обычными таблицами решается, типа R(date1,data2)=F[days(date2)]-F[days(date1)] где F строится заранее по производственному календарю или графику работ. MH>Часть из кэйсов наверно можно загнать чтоб в sql делались, MH>но не все и с учетом того что выборка даже всего объёма быстро
using System;
using System.Diagnostics;
class Test {
static void rate(int mf,Action act) {
long n=1,tau=0, tau_min=160, mg=10;
for(;;) {
var sw = Stopwatch.StartNew();
for (long i = n; i > 0; --i) {
act(); act(); act(); act(); act();
act(); act(); act(); act(); act();
}
sw.Stop();
tau = sw.ElapsedMilliseconds;
if (tau > tau_min) break;
n = n * 10;
}
n *= mg;
Console.WriteLine("rate={0:G3}MHz dt={1:G5}ns x{2}", n * mf * 1e-3 / tau,tau * 1e6 / n / mf, mf);
}
static void rate_group(Action act) {
rate(1,act);
rate(1,() => { act(); });
rate(10,() => { for (int i = 0; i < 10; i++) act(); });
rate(100,() => { for (int i = 0; i < 100; i++) act(); });
rate(1000,() => { for (int i = 0; i < 1000; i++) act(); });
}
static string fn(string a,string b) {
a = a.ToLower();
b = b.ToUpper();
return string.Format("{0}{1}",a,b);
}
static double fn(double a,double b) {
return 2 + a * Math.Pow(Math.Sin(Math.E),2) + Math.Pow(Math.Cos(Math.E), 2) * b;
}
public static void Main() {
rate(1,() => { });
/*
int x = 0;
rate(1,() => { x++; });
rate(10,() => { for (int i = 0; i < 10; i++) x++; });
rate(100,() => { for (int i = 0; i < 100; i++) x++; });
rate(1000,() => { for (int i = 0; i < 1000; i++) x++; });
rate_group(() => { x++; });
*/var p = new Parser("2+2*sin(e)^2+cos(e)^2*2");
var e = new Expression().init();
var dc = new DynamicCode();
dc.load();
var mi=dc.CompileMethod("A.fn",@"
using System;
public class A {
public static double fn(double a,double b) {
double r= 2 + a * Math.Pow(Math.Sin(Math.E),2) + Math.Pow(Math.Cos(Math.E), 2) * b;
return r;
}
}
");
var mi100 = dc.CompileMethod("A.fn",@"
using System;
public class A {
public static double fn(double a,double b) {
double r=0;
for(int i=0;i<100;i++) {
r = 2 + a * Math.Pow(Math.Sin(Math.E),2) + Math.Pow(Math.Cos(Math.E), 2) * b;
}
return r;
}
}
");
Console.WriteLine("native");
rate_group(() => { var r = fn(2,2); });
Console.WriteLine("parsed");
rate_group(() => { var r = e.Eval(p.reset()); });
Console.WriteLine("compiled");
rate_group(() => { var r = (double)mi.Invoke(null,new object[] { 2, 2 } ); });
Console.WriteLine("compiled batch");
rate(100,() => { var r = (double)mi100.Invoke(null,new object[] { 2,2 }); });
dc.unload();
}
}
DynamicCode.cs
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Microsoft.CSharp;
public class DynamicCode {
AppDomain domain;
string DomainName = "MyDomain";
string References = "System.dll System.Data.dll";
public void load() {
domain = AppDomain.CreateDomain(DomainName);
domain.DomainUnload += (s,a) => Console.WriteLine("unload domain");
}
public void unload() {
AppDomain.Unload(domain);
domain = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
public Module Compile(string code) {
var prm = new CompilerParameters();
prm.GenerateInMemory = true;
prm.TreatWarningsAsErrors = false;
prm.GenerateExecutable = false;
prm.CompilerOptions = "/optimize";
prm.OutputAssembly = DomainName;
prm.ReferencedAssemblies.AddRange(References.Split(' '));
var provider = new CSharpCodeProvider();
var compile = provider.CompileAssemblyFromSource(prm,new string[] { code });
if (compile.Errors.HasErrors) {
var sb = new StringBuilder();
sb.Append("Compile error: ");
foreach (var ce in compile.Errors) {
sb.Append("\r\n");
sb.Append(ce.ToString());
}
throw new Exception(sb.ToString());
}
var modules = compile.CompiledAssembly.GetModules();
return modules[0];
}
public MethodInfo CompileMethod(string name,string code) {
var parts = name.Split('.'); if (parts.Length != 2) throw new Exception("invalid name");
var mod = Compile(code); if (mod == null) throw new Exception("no module");
var mt = mod.GetType(parts[0]); if (mt == null) throw new Exception("no type " + parts[0]);
var mi = mt.GetMethod(parts[1]); if (mi == null) throw new Exception("no method " + parts[1]);
RuntimeHelpers.PrepareMethod(mi.MethodHandle);
return mi;
}
}
Expression.cs
using System;
using System.Collections.Generic;
public class Expression {
Parser p;
public Parser parser { get { return p; } }
public Dictionary<string,Func<double[],double>> functions = new Dictionary<string,Func<double[],double>>();
public Dictionary<string,double> constants = new Dictionary<string,double>();
public Expression init() {
functions["abs"] = x => Math.Abs(x[0]);
functions["sin"] = x => Math.Sin(x[0]);
functions["asin"] = x => Math.Asin(x[0]);
functions["cos"] = x => Math.Cos(x[0]);
functions["acos"] = x => Math.Acos(x[0]);
functions["tan"] = x => Math.Tan(x[0]);
functions["log"] = x => Math.Log(x[0]);
functions["exp"] = x => Math.Exp(x[0]);
functions["pow"] = x => Math.Pow(x[0],x[1]);
functions["round"] = x => Math.Round(x[0]);
functions["ceil"] = x => Math.Ceiling(x[0]);
functions["floor"] = x => Math.Floor(x[0]);
constants["pi"] = Math.PI;
constants["e"] = Math.E;
return this;
}
public double Eval(Parser p) {
this.p = p; double res = L0();
if (p.anyError) throw new Exception(p.ErrorMessage);
return res;
}
double L0() {
double r = L1();
while ((!p.eos) && (!p.anyError)) {
p.skipSpace();
if (p.chk('+')) { r += L1(); continue; }
if (p.chk('-')) { r -= L1(); continue; }
break;
}
return r;
}
double L1() {
double r = L2();
while (!p.eos && !p.anyError) {
p.skipSpace();
if (p.chk('*')) { r *= L2(); continue; }
if (p.chk('/')) {
double r1 = L2();
if (r1 == 0) { p.error("divide by zero"); return 0; }
r /= r1;
continue;
}
break;
}
return r;
}
double L2() {
double r = L3(); if (p.anyError) return r;
if (p.skipSpace().chk('^')) r = Math.Pow(r,L3());
return r;
}
double L3() {
double r;
if (p.skipSpace().chk('(')) {
r = L0();
if (!p.skipSpace().chk(')')) { p.error("no closing bracket"); return 0; }
} else {
r = L4();
}
return r;
}
double L4() {
if (!Parser.isAlpha(p.skipSpace().peek())) return L5();
string name; double r;
p.getName(out name).skipSpace();
if (p.chk('(')) {
Func<double[],double> fn;
if (!functions.TryGetValue(name,out fn)) { p.error("unknown function " + name); return 0; }
List<double> args = new List<double>();
for (; ; ) {
p.skipSpace();
if (p.eos) { p.error("unexpected end of expression"); return 0; }
if (p.chk(')')) break;
double arg = L0(); if (p.anyError) return 0;
args.Add(arg);
}
r = fn(args.ToArray());
} else {
if (!constants.TryGetValue(name,out r)) { p.error("unknown constant " + name); return 0; }
}
return r;
}
double L5() {
if (p.skipSpace().chk('-')) return -L6();
return L6();
}
double L6() {
double r;
p.skipSpace().getDouble(out r);
return r;
}
}
Parser.cs
using System;
using System.Collections.Generic;
using System.Text;
public class Parser {
string src,err_msg; int pos,head,tail,err;
public Parser(string text) {
src = text;
head = 0; tail = src.Length;
pos = 0; err = -1;
}
public string ErrorMessage {
get {
if (err < 0) return"";
return String.Format("{0} {1}",err,err_msg);
}
}
public Parser error(string msg) { if (err < 0) { err = pos; err_msg = msg; } return this; }
public Parser clearError() { err = -1; return this; }
public bool anyError { get { return err >= 0; } }
static public bool isSpace(char c) { return c > (char)0 && c <= ' '; }
static public bool isDigit(char c) { return c >= '0' && c <= '9'; }
static public bool isLetter(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); }
static public bool isHex(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); }
static public bool isAlpha(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'); }
static public bool isAlphaDigit(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '_'); }
public Parser reset() { pos = head; err = -1; return this; }
public bool eos { get { return pos >= tail; } }
public char peek() { return pos < tail ? src[pos] : (char)0; }
public char get() { return pos < tail ? src[pos++] : (char)0; }
public bool chk(char c) { if (pos < tail && src[pos] == c) { pos++; return true; } return false; }
public Parser skipSpace() { while (pos < tail && isSpace(src[pos])) pos++; return this; }
public Parser getName(out string name) {
name = "";
if (!isAlpha(peek())) return error("invalid name format");
var sb = new StringBuilder();
while (isAlphaDigit(peek())) sb.Append(get());
name = sb.ToString();
return this;
}
public Parser getUint(out uint value) {
value = 0;
if (!isDigit(peek())) return error("invalid digit format");
uint r1 = 0,r2 = 0;
while (isDigit(peek())) {
r1 = r2;
r2 = r2 * 10 + (uint)get() - (uint)'0';
if (r2 < r1) return error("integer overflow");
}
value = r2;
return this;
}
public Parser getInt(out int value) {
value = 0;
bool sign = chk('-');
uint r1; int r2;
if (getUint(out r1).anyError) return this;
r2 = (int)r1; if (r2 < 0) return error("integer overflow");
if (sign) r2 = -r2;
value = r2;
return this;
}
public Parser getDouble(out double value) {
value = 0;
bool sign = chk('-'); if (!sign) chk('+');
if (!isDigit(peek())) return error("invalid double format");
double r1 = 0;
while (isDigit(peek())) { r1 = r1 * 10 + (int)get() - (int)'0'; }
if (chk('.')) {
double r2 = 0,r3 = 1;
while (isDigit(peek())) { r3 = r3 / 10; r2 += r3 * ((int)get() - (int)'0'); }
r1 += r2;
}
if (chk('e') || chk('E') || chk('d') || chk('D')) {
bool esign = chk('-'); if (!esign) chk('+');
if (!isDigit(peek())) return error("invalid double exponent format");
double r4 = 0;
while (isDigit(peek())) { r4 = r4 * 10 + (int)get() - (int)'0'; }
if (esign) r4 = -r4;
r1 *= Math.Pow(10,r4);
}
value = r1;
return this;
}
}
MH>, время работы рабочей функции — быстро,
Сколько тактов процессора? Или хотя бы ms обрабатывается функция. MH>заметная часть времени из общей работы тратиться именно на прогон через интерпретатор — он слабое место.
Или оптимизируйте интерпретатор, или делайте компиляцию в исполняемую функции и вызывайте её.
Тут еще можно рассмотреть обработку не по одному элементу, а группами (batch-ами).
fn1(in,out) -> fnN(in,out,count)
Фильтры могут выбирать элементы, могут формировать новые списки, а могу и агрегировать что-либо (суммировать, считать количество и т.п.)
От этого будут различные способы разбиения для выполнения в паралельных потоках.
Так же данные можно организовать по разному: в виде объектов и в виде потоков.
_>>>Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду. MH>>Ваш пример тоже встречается) сходу вспомню ещё что-то типа с датами: кол-во рабочих дней между заданными датами с учетом производственного календаря и графика работ. _>Это обычными таблицами решается, типа R(date1,data2)=F[days(date2)]-F[days(date1)] где F строится заранее по производственному календарю или графику работ.
да наверняка тут можно придумать решение за счет sql, но это один из примеров, функций много и все переносить на sql не вариант.
и есть варианты работы фильтров выражений по уже кэшированным данным. вообщем — разгонять скорость выполнения выражений фильтров надо всё равно.
MH>>но не все и с учетом того что выборка даже всего объёма быстро _>[cut=Что значит быстро 100Мб/сек, 1000Мб/сек?]
быстро, это порядка 10-15% от общего времени работы.
_>
спасибо за тесты, интересно! но тут вызов через рефлексию портит картину, он медленный и не показывает реальной разницы между compiled и parsed.
у нас используется компиленное выражение для вызова функции, что сильно ускоряет её вызов в сравнении через вариант с .Invoke
MH>>, время работы рабочей функции — быстро, _>Сколько тактов процессора? Или хотя бы ms обрабатывается функция.
порядка 5-10% от общего времени работы фильтрации. у меня данные специально на простых быстрых функциях, чтоб именно потери проявились на интерпретаторе.
кстати ваши тесты показывают сходный результат, время прямого вызова функции на порядок быстрее чем через .Invoke даже скомпилированного варианта.
_>Фильтры могут выбирать элементы, могут формировать новые списки, а могу и агрегировать что-либо (суммировать, считать количество и т.п.) _>От этого будут различные способы разбиения для выполнения в паралельных потоках.
да, организовать паралельную работу фильтрации это интересный вариант, можно будет и его потом сделать.
тк получение входных данных для фильтрации существенно опережает процесс самой фильтрации, то как-то бы применить тут паралельную обработку пачек данных.
я тут вижу вариант с Where из PLinq (что проще сделать, но хз как в сравнении со 2-м вариантом), либо (так как входные данные на пачки можем бить сами) то что-то типа техники паралеллализм по возможности
..
_>Так же данные можно организовать по разному: в виде объектов и в виде потоков.
это я не понял, что значит в виде потоков?
исходные данные сейчас у нас для фильтрации — IEnumerable<_>
Здравствуйте, MadHuman, Вы писали:
MH>исходные данные сейчас у нас для фильтрации — IEnumerable<_>
IEnumerator подразумевает вызов MoveNext для получения следующих данных по запросу. Это дополнительные расходы. Обычно если вычислений много и однотипных
работают с batch-ами сразу группами по N штук. Если N большое и вычисления независимые то такая функция может сама распаралеливать работу. MH>это я не понял, что значит в виде потоков?
Имеется ввиду что данные сереализуются в бинарный поток, пачками. И так же обрабатываются как большой участок памяти и потом так же разворачиваются, при этом данные могут быть частично пожаты что может увеличить скорость если упираемся в пропускную способность памяти. Особенно если данных много этот вариант тоже желательно рассмотреть. Более того бинарный поток проще свалить для вычисления во внешние программы.
Здравствуйте, MadHuman, Вы писали: MH>кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере MH>всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений.
Для такого случая можно сделать отдельный AppDomain.
Вообще, эти temporary assemblies будут лежать в виде файлов на диске и менеджер памяти должен их страницы выкинуть при необходимости.