разгон вычислений
От: MadHuman Россия  
Дата: 06.06.19 15:08
Оценка:
Всем привет!

Есть определённые пользовательские выражения с тривиальными возможностями (скобки, вызовы функций, доступ к переменным), есть парсер — парсит строку в AST,
есть простой интерпретатор, ему на вход AST и enviroment, внутри обход AST и для каждого узла выполняется соотвествующее ему действия.
Стоит задача разогнать скорость выполнения, тк при большом кол-ве повторений скорости интерпретатора уже недостаточно.

Какие подходы на мой взгляд возможны:
1. по AST сформировать функцию-делегат, которая является композицией используемых операций. до конца ещё не понятно как сделать, но где-то слышал про технику.

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

3. заморочится за прям генерацию MSIL, но это видится сильно сложнее чем 2, и есть ощущение что не будет сильно быстрее..


Кто сталкивался с подобными задачами или имеет понимание, какой подход лучше выбрать и почему?
Re: разгон вычислений
От: Mihas  
Дата: 06.06.19 15:30
Оценка:
Здравствуйте, MadHuman, Вы писали:


MH>2. собрать лямбда выражение и компильнуть его.

MH>тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений.
Может быть, выяснить? Обложить юзингами. Попрофилировать.

MH>также слышал, что компиляция лямбд сильно не быстрый процесс и при частом выполнении разных выражений может стать проблемой..

Я использовал для аналогичных целей IronPython. Время, затрачиваемое на интерпретирацию каждого выражения измерялось миллисекундами, ни как не меньше.
Кэшировать не получится? Часто ли меняются выражения?
Re: разгон вычислений
От: IT Россия linq2db.com
Дата: 06.06.19 16:08
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>2. собрать лямбда выражение и компильнуть его.

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

Из перечисленного однозначно 2. Скорость сравнима с рукописным кодом. Расход памяти следует контролировать кешированием.

Ещё один вариант — генерировать C# код и компилировать его в runtime. Гуглить по compiler as a service.
Если нам не помогут, то мы тоже никого не пощадим.
Re: разгон вычислений
От: kov_serg Россия  
Дата: 06.06.19 16:23
Оценка:
Здравствуйте, 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 потоков распаралелить)
Re: разгон вычислений
От: RushDevion Россия  
Дата: 06.06.19 19:22
Оценка: 2 (1)
MH>Кто сталкивался с подобными задачами или имеет понимание, какой подход лучше выбрать и почему?

Я писал что-то похожее.
Тоже по пользовательской строке с выражением и контексту нужно было возвращать true/false.
Делал через Expressions с компиляцией в лямбду.
Внешний интерфейс был примерно таким:
Func<T, bool> AST.Compile<T>(string expression, T context);

Поверх этого — кэш по связке expression+T.
У меня было всего несколько возможных типов контекста и expressions менялись не часто, так что кэшем вопрос памяти закрылся.
Поспрашивай гугл по ключевым словам "C# rules engine", наверняка или готовая штука найдется или что-то похожее с открытым кодом, чтобы взять за основу.
Re: разгон вычислений
От: Эйнсток Файр Мухосранск Странный реагент
Дата: 06.06.19 21:57
Оценка: -1 :)
MH>Какие подходы на мой взгляд возможны:
4. Сгенерировать бинарный код для целевого CPU (вот прямо команды по битам, как в Developers Manual)
5. Сгенерировать код для GPU
Re[2]: разгон вычислений
От: kov_serg Россия  
Дата: 06.06.19 22:19
Оценка: 6 (1) +3 :)))
Здравствуйте, Эйнсток Файр, Вы писали:

MH>>Какие подходы на мой взгляд возможны:

ЭФ>4. Сгенерировать бинарный код для целевого CPU (вот прямо команды по битам, как в Developers Manual)
ЭФ>5. Сгенерировать код для GPU
6. Синтезировать вентили для FPGA
Re[3]: разгон вычислений
От: Эйнсток Файр Мухосранск Странный реагент
Дата: 06.06.19 23:14
Оценка:
_>6. Синтезировать вентили для FPGA

-Долго ждёте?
-Вообще-то уже две минуты, ты мог бы и побыстрее.
-Извините, больше такое не повторится. Я, наверное, слишком долго ел, и медленно одевался, простите.
-И бежал не так быстро.
-И бежал не так быстро. — Согласился с упрёком я.

Re: разгон вычислений
От: alexanderfedin США http://alexander-fedin.pixels.com/
Дата: 21.06.19 01:43
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>2. собрать лямбда выражение и компильнуть его.

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

MH>3. заморочится за прям генерацию MSIL, но это видится сильно сложнее чем 2, и есть ощущение что не будет сильно быстрее..


MH>Кто сталкивался с подобными задачами или имеет понимание, какой подход лучше выбрать и почему?

1. Собрать лямбду
2. Сделать кэш вида исходная-строка=>компилированная-лямбда
3. К кэше использовать ConditionalWeakTable и больше не переживать о памяти.
Respectfully,
Alexander Fedin.
Re[2]: разгон вычислений
От: MadHuman Россия  
Дата: 23.06.19 12:04
Оценка: :)
Здравствуйте, alexanderfedin, Вы писали:

MH>>тут смущает, что слышал, что вроде результаты таких компиляций остаются в памяти и соотвествеено есть риск чрезмерного её расхода при большом и частом количестве вычислений.


A>2. Сделать кэш вида исходная-строка=>компилированная-лямбда

A>3. К кэше использовать ConditionalWeakTable и больше не переживать о памяти.
кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере
всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений.
Отредактировано 23.06.2019 12:15 MadHuman . Предыдущая версия .
Re[3]: разгон вычислений
От: kov_serg Россия  
Дата: 23.06.19 16:16
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере

MH>всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений.
А вы их в отдельный домен складывайте и потом выгружайте домен со всеми его ин-мемори ассемблями.
Re[4]: разгон вычислений
От: MadHuman Россия  
Дата: 23.06.19 16:31
Оценка:
Здравствуйте, kov_serg, Вы писали:

MH>>кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере

MH>>всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений.
_>А вы их в отдельный домен складывайте и потом выгружайте домен со всеми его ин-мемори ассемблями.
дак кросс-доменные вызовы маршалинга потребуют, перфоманс хуже. за перфоманс же и боремся
Re[5]: разгон вычислений
От: kov_serg Россия  
Дата: 23.06.19 17:10
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>дак кросс-доменные вызовы маршалинга потребуют, перфоманс хуже. за перфоманс же и боремся

Что мешает все вычисления в отдельном домене проводить?
А пример решаемой проблемы можно привести? (Объёмы и организация данных и количество проводимых вычислений).
Какой перфоманс требуется и какой достигнут? ( данных/сек, выч/сек )
Re[6]: разгон вычислений
От: MadHuman Россия  
Дата: 23.06.19 17:44
Оценка:
Здравствуйте, kov_serg, Вы писали:

MH>>дак кросс-доменные вызовы маршалинга потребуют, перфоманс хуже. за перфоманс же и боремся

_>Что мешает все вычисления в отдельном домене проводить?
ну, если задаться такой целью, то наверно это решаемо. пока неясно сколько будет трудностей, и прежде надо получить подтверждение/опровержение гипотезы которая на этот путь ведёт (что эти ассембли не выгружаются).

_>А пример решаемой проблемы можно привести? (Объёмы и организация данных и количество проводимых вычислений).

это обычно кастомные фильтры по исходным данным в которых используются функции не ложащиеся на sql, что вынуждает фильтрацию проводить на клиенте.


_>Какой перфоманс требуется и какой достигнут? ( данных/сек, выч/сек )

проблемы (время фильтрации десятки секунд) появляются при обработке порядка миллионов записей. значительная часть времени сейчас именно работа фильтров.
хотелось бы секунды. по предварительным оценкам разогнать на порядок (а может и два) возможно.
Re[7]: разгон вычислений
От: kov_serg Россия  
Дата: 23.06.19 21:28
Оценка:
Здравствуйте, MadHuman, Вы писали:

_>>А пример решаемой проблемы можно привести? (Объёмы и организация данных и количество проводимых вычислений).

MH>это обычно кастомные фильтры по исходным данным в которых используются функции не ложащиеся на sql, что вынуждает фильтрацию проводить на клиенте.
Надо точно или быстро? Может имеет смысл использовать выборки или интегральные величины или спец индексы? Все данные висят в озу или раз неложаться в sql то выниаются из базы?

MH>проблемы (время фильтрации десятки секунд) появляются при обработке порядка миллионов записей. значительная часть времени сейчас именно работа фильтров.

MH>хотелось бы секунды. по предварительным оценкам разогнать на порядок (а может и два) возможно.
Каков размер одной записи с которой работает фильтр по сравнению с 4Кб.
Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду.
Re[8]: разгон вычислений
От: MadHuman Россия  
Дата: 24.06.19 06:58
Оценка:
Здравствуйте, kov_serg, Вы писали:


MH>>это обычно кастомные фильтры по исходным данным в которых используются функции не ложащиеся на sql, что вынуждает фильтрацию проводить на клиенте.

_>Надо точно или быстро?
надо быстро и точно)
_>Может имеет смысл использовать выборки или интегральные величины или спец индексы?
там где это становится совсем критично, строим спец индексы.
но тк это кастомные фильтры, постоянно меняются, разнообразие достаточно велико, выявлять проблемные места в таких условиях непросто.
также это могут быть и просто какие-то временные фильтры, которые пару раз будут использованы.
Вообщем задача разгона вычислений фильтров всё равно актуальна, также это снизит CPU нагрузку на сервера, что актуально.

_>Все данные висят в озу или раз неложаться в sql то выниаются из базы?

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

_>Каков размер одной записи с которой работает фильтр по сравнению с 4Кб.

существенно меньше, на модельных примерах это было несколько идшек и числовых данных (long).

_>Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду.

Ваш пример тоже встречается) сходу вспомню ещё что-то типа с датами: кол-во рабочих дней между заданными датами с учетом производственного календаря и графика работ.
есть ещё много примеров, но сейчас не вспомню. Часть из кэйсов наверно можно загнать чтоб в sql делались, но не все и с учетом того что выборка даже всего объёма быстро, время работы рабочей функции — быстро,
заметная часть времени из общей работы тратиться именно на прогон через интерпретатор — он слабое место.
Re[9]: разгон вычислений
От: kov_serg Россия  
Дата: 24.06.19 14:20
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>вынимаются из базы, но вынимается узкая выборка (только необходимые для дальнейшего использования поля), что происходит довольно быстро.


_>>Каков размер одной записи с которой работает фильтр по сравнению с 4Кб.

MH>существенно меньше, на модельных примерах это было несколько идшек и числовых данных (long).

_>>Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду.

MH>Ваш пример тоже встречается) сходу вспомню ещё что-то типа с датами: кол-во рабочих дней между заданными датами с учетом производственного календаря и графика работ.
Это обычными таблицами решается, типа R(date1,data2)=F[days(date2)]-F[days(date1)] где F строится заранее по производственному календарю или графику работ.

MH>Часть из кэйсов наверно можно загнать чтоб в sql делались,

MH>но не все и с учетом того что выборка даже всего объёма быстро
  Что значит быстро 100Мб/сек, 1000Мб/сек?
rate=333MHz dt=3ns x1
native
rate=5.08MHz dt=197ns x1
rate=5.43MHz dt=184ns x1
rate=5.41MHz dt=185ns x10
rate=5.32MHz dt=188ns x100
rate=5.24MHz dt=191ns x1000
parsed
rate=0.701MHz dt=1427ns x1
rate=0.68MHz dt=1471ns x1
rate=0.686MHz dt=1457ns x10
rate=0.678MHz dt=1474ns x100
rate=0.681MHz dt=1468ns x1000
compiled
rate=0.692MHz dt=1446ns x1
rate=0.685MHz dt=1459ns x1
rate=0.691MHz dt=1448ns x10
rate=0.697MHz dt=1435ns x100
rate=0.686MHz dt=1458ns x1000
compiled batch
rate=5.08MHz dt=197ns x100
unload domain

  Test.cs
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)
Фильтры могут выбирать элементы, могут формировать новые списки, а могу и агрегировать что-либо (суммировать, считать количество и т.п.)
От этого будут различные способы разбиения для выполнения в паралельных потоках.
Так же данные можно организовать по разному: в виде объектов и в виде потоков.
Re[10]: разгон вычислений
От: MadHuman Россия  
Дата: 24.06.19 15:54
Оценка:
Здравствуйте, kov_serg, Вы писали:


_>>>Пример данных и фильтра мог бы прояснить происходящее. Может вы там регулярками приводите название улиц и телефонов к каноническому виду.

MH>>Ваш пример тоже встречается) сходу вспомню ещё что-то типа с датами: кол-во рабочих дней между заданными датами с учетом производственного календаря и графика работ.
_>Это обычными таблицами решается, типа R(date1,data2)=F[days(date2)]-F[days(date1)] где F строится заранее по производственному календарю или графику работ.
да наверняка тут можно придумать решение за счет sql, но это один из примеров, функций много и все переносить на sql не вариант.
и есть варианты работы фильтров выражений по уже кэшированным данным. вообщем — разгонять скорость выполнения выражений фильтров надо всё равно.

MH>>но не все и с учетом того что выборка даже всего объёма быстро

_>[cut=Что значит быстро 100Мб/сек, 1000Мб/сек?]
быстро, это порядка 10-15% от общего времени работы.

_>
_>rate=333MHz dt=3ns x1
_>native
_>rate=5.08MHz dt=197ns x1
_>rate=5.43MHz dt=184ns x1
_>rate=5.41MHz dt=185ns x10
_>rate=5.32MHz dt=188ns x100
_>rate=5.24MHz dt=191ns x1000
_>parsed
_>rate=0.701MHz dt=1427ns x1
_>rate=0.68MHz dt=1471ns x1
_>rate=0.686MHz dt=1457ns x10
_>rate=0.678MHz dt=1474ns x100
_>rate=0.681MHz dt=1468ns x1000
_>compiled
_>rate=0.692MHz dt=1446ns x1
_>rate=0.685MHz dt=1459ns x1
_>rate=0.691MHz dt=1448ns x10
_>rate=0.697MHz dt=1435ns x100
_>rate=0.686MHz dt=1458ns x1000
_>compiled batch
_>rate=5.08MHz dt=197ns x100
_>unload domain
_>

спасибо за тесты, интересно! но тут вызов через рефлексию портит картину, он медленный и не показывает реальной разницы между compiled и parsed.
у нас используется компиленное выражение для вызова функции, что сильно ускоряет её вызов в сравнении через вариант с .Invoke


MH>>, время работы рабочей функции — быстро,

_>Сколько тактов процессора? Или хотя бы ms обрабатывается функция.
порядка 5-10% от общего времени работы фильтрации. у меня данные специально на простых быстрых функциях, чтоб именно потери проявились на интерпретаторе.
кстати ваши тесты показывают сходный результат, время прямого вызова функции на порядок быстрее чем через .Invoke даже скомпилированного варианта.


_>Фильтры могут выбирать элементы, могут формировать новые списки, а могу и агрегировать что-либо (суммировать, считать количество и т.п.)

_>От этого будут различные способы разбиения для выполнения в паралельных потоках.
да, организовать паралельную работу фильтрации это интересный вариант, можно будет и его потом сделать.
тк получение входных данных для фильтрации существенно опережает процесс самой фильтрации, то как-то бы применить тут паралельную обработку пачек данных.
я тут вижу вариант с Where из PLinq (что проще сделать, но хз как в сравнении со 2-м вариантом), либо (так как входные данные на пачки можем бить сами) то что-то типа техники паралеллализм по возможности
Автор: MadHuman
Дата: 06.05.17
..

_>Так же данные можно организовать по разному: в виде объектов и в виде потоков.

это я не понял, что значит в виде потоков?
исходные данные сейчас у нас для фильтрации — IEnumerable<_>
Re[11]: разгон вычислений
От: kov_serg Россия  
Дата: 24.06.19 17:51
Оценка:
Здравствуйте, MadHuman, Вы писали:

MH>исходные данные сейчас у нас для фильтрации — IEnumerable<_>

IEnumerator подразумевает вызов MoveNext для получения следующих данных по запросу. Это дополнительные расходы. Обычно если вычислений много и однотипных
работают с batch-ами сразу группами по N штук. Если N большое и вычисления независимые то такая функция может сама распаралеливать работу.
MH>это я не понял, что значит в виде потоков?
Имеется ввиду что данные сереализуются в бинарный поток, пачками. И так же обрабатываются как большой участок памяти и потом так же разворачиваются, при этом данные могут быть частично пожаты что может увеличить скорость если упираемся в пропускную способность памяти. Особенно если данных много этот вариант тоже желательно рассмотреть. Более того бинарный поток проще свалить для вычисления во внешние программы.
Re[3]: разгон вычислений
От: alexanderfedin США http://alexander-fedin.pixels.com/
Дата: 28.06.19 14:47
Оценка:
Здравствуйте, MadHuman, Вы писали:
MH>кэш организовать не проблема, проблема что результат компиляции этих лямбд это временные ин-мемори ассембли, которые не выгружаются из памяти даже при потере
MH>всех ссылок на делегат (но это не точно), что и ведет к риску чрезмерного расхода памяти при большом разнообразии вариантов исходных выражений.
Для такого случая можно сделать отдельный AppDomain.
Вообще, эти temporary assemblies будут лежать в виде файлов на диске и менеджер памяти должен их страницы выкинуть при необходимости.
Respectfully,
Alexander Fedin.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.