OberonCore https://forum.oberoncore.ru/ |
|
Виртуальная машина на CP https://forum.oberoncore.ru/viewtopic.php?f=29&t=6400 |
Страница 1 из 3 |
Автор: | ilovb [ Вторник, 21 Май, 2019 21:13 ] |
Заголовок сообщения: | Виртуальная машина на CP |
Задумал я тут ВМ написать (под скриптовый DSL для определенных нужд) Идея простая как бревно. Есть универсальный интерфейс Value для любых значений. Значениями могут быть числа с плавающей точкой, строки, объекты etc. (в примере только числа) Машина предполагается регистровая. Каждая инструкция 4 байта. Я написал простой тестовый пример. Надеюсь по коду понятно что происходит. Это я руками сделал тупую компиляцию следующего: Код: x = 0 y = 1 while x <= 100000000 do x = x + y end Как улучшить следующий подход? Можно ли избежать лишней работы с кучей не прибегая к union? Больше всего меня интересует скорость исполнения байткода. Этот пример у меня выполняется 6 сек., что слишком долго. ps Просьба не обращать внимание на стиль, т.к. написано левой пяткой только ради эксперимента. Код: MODULE MyTest;
IMPORT Log := StdLog; CONST Add = 0; Set = 1; Get = 2; Jmp = 3; Jgt = 4; End = 5; TYPE Value = POINTER TO EXTENSIBLE RECORD END; Number = POINTER TO RECORD(Value) r: REAL; END; PROCEDURE (v: Value) Num(): REAL, NEW, EXTENSIBLE; BEGIN RETURN 0; END Num; PROCEDURE (v: Value) SetReal(x: REAL), NEW, EXTENSIBLE; BEGIN END SetReal; PROCEDURE (v: Value) Set(x: Value), NEW, EXTENSIBLE; BEGIN END Set; PROCEDURE (v: Number) Num(): REAL; BEGIN RETURN v.r; END Num; PROCEDURE (v: Number) SetReal(x: REAL); BEGIN v.r := x; END SetReal; PROCEDURE (v: Number) Set(x: Value); BEGIN v.r := x.Num(); END Set; PROCEDURE emit1(b1, b2, b3, b4: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24) + ASH(b2, 16) + ASH(b3, 8) + b4; RETURN w; END emit1; PROCEDURE emit2(b1, b2, b4: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24) + ASH(b2, 16) + b4; RETURN w; END emit2; PROCEDURE emit3(b1, b4: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24) + b4; RETURN w; END emit3; PROCEDURE emit4(b1: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24); RETURN w; END emit4; PROCEDURE Do*; VAR w: INTEGER; b1, b2, b3, b4: INTEGER; ip: INTEGER; ops: ARRAY 13 OF INTEGER; reg: ARRAY 256 OF Value; mem: ARRAY 1000 OF Value; r: REAL; n: Number; i: INTEGER; BEGIN (* помещение программы в память кода *) ops[00] := emit2(Get, 0, 0); (* 0 *) ops[01] := emit2(Set, 0, 10); (* x = 0 *) ops[02] := emit2(Get, 0, 1); (* 1 *) ops[03] := emit2(Set, 0, 11); (* y = 1 *) ops[04] := emit2(Get, 0, 10); (* x *) ops[05] := emit2(Get, 1, 2); (* 100000000 *) ops[06] := emit3(Jgt, 11); (* if x > 100000000 goto *) ops[07] := emit2(Get, 0, 10); (* x *) ops[08] := emit2(Get, 1, 11); (* y *) ops[09] := emit1(Add, 0, 0, 1); (* _ = x + y *) ops[10] := emit2(Set, 0, 10); (* x = _ *) ops[11] := emit3(Jmp, 3); (* goto *) ops[12] := emit4(End); (* end *) (* подготовка регистров *) FOR i := 0 TO LEN(reg) - 1 DO NEW(n); n.r := 0; reg[i] := n; END; (* подготовка памяти данных *) FOR i := 0 TO 20 DO NEW(n); n.r := 0; mem[i] := n; END; (* размещение констант в памяти *) NEW(n); n.r := 0; mem[0] := n; NEW(n); n.r := 1; mem[1] := n; NEW(n); n.r := 100000000; mem[2] := n; (* цикл ВМ *) ip := 0; i := 0; LOOP w := ops[ip]; b4 := w MOD 0100H; b3 := w DIV 0100H MOD 0100H; b2 := w DIV 010000H MOD 0100H; b1 := w DIV 01000000H MOD 0100H; CASE b1 OF |Get: reg[b2].Set(mem[b4]); |Set: mem[b4].Set(reg[b2]); |Add: reg[b2].SetReal(reg[b3].Num() + reg[b4].Num()); |Jmp: ip := b4; |Jgt: IF reg[0].Num() > reg[1].Num() THEN ip := b4 END; |End: EXIT; END; INC(ip); END; END Do; END MyTest.Do DevCompiler.CompileThis MyTest!! DevDebug.UnloadThis MyTest |
Автор: | ilovb [ Вторник, 21 Май, 2019 21:40 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Вот еще до кучи накидал вариант с union: Код: MODULE MyTest; IMPORT Log := StdLog, SYSTEM; CONST Add = 0; Set = 1; Get = 2; Jmp = 3; Jgt = 4; End = 5; TYPE Value = RECORD type: INTEGER; data: RECORD [union] r: REAL; s: POINTER [untagged] TO ARRAY OF CHAR; END; END; PROCEDURE emit1(b1, b2, b3, b4: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24) + ASH(b2, 16) + ASH(b3, 8) + b4; RETURN w; END emit1; PROCEDURE emit2(b1, b2, b4: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24) + ASH(b2, 16) + b4; RETURN w; END emit2; PROCEDURE emit3(b1, b4: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24) + b4; RETURN w; END emit3; PROCEDURE emit4(b1: INTEGER): INTEGER; VAR w: INTEGER; BEGIN w := ASH(b1, 24); RETURN w; END emit4; PROCEDURE Do*; VAR w: INTEGER; b1, b2, b3, b4: INTEGER; ip: INTEGER; ops: ARRAY 13 OF INTEGER; reg: POINTER TO ARRAY 256 OF Value; mem: POINTER TO ARRAY 1000 OF Value; BEGIN ops[00] := emit2(Get, 0, 0); (* 0 *) ops[01] := emit2(Set, 0, 10); (* x = 0 *) ops[02] := emit2(Get, 0, 1); (* 1 *) ops[03] := emit2(Set, 0, 11); (* y = 1 *) ops[04] := emit2(Get, 0, 10); (* x *) ops[05] := emit2(Get, 1, 2); (* 100000000 *) ops[06] := emit3(Jgt, 11); (* if x > 100000000 goto *) ops[07] := emit2(Get, 0, 10); (* x *) ops[08] := emit2(Get, 1, 11); (* y *) ops[09] := emit1(Add, 0, 0, 1); (* _ = x + y *) ops[10] := emit2(Set, 0, 10); (* x = _ *) ops[11] := emit3(Jmp, 3); (* goto *) ops[12] := emit4(End); (* end *) NEW(reg); NEW(mem); mem[0].data.r := 0; mem[1].data.r := 1; mem[2].data.r := 100000000; ip := 0; LOOP w := ops[ip]; b4 := w MOD 0100H; b3 := w DIV 0100H MOD 0100H; b2 := w DIV 010000H MOD 0100H; b1 := w DIV 01000000H MOD 0100H; CASE b1 OF |Get: reg[b2] := mem[b4]; |Set: mem[b4] := reg[b2]; |Add: reg[b2].data.r := reg[b3].data.r + reg[b4].data.r; |Jmp: ip := b4; |Jgt: IF reg[0].data.r > reg[1].data.r THEN ip := b4 END; |End: EXIT; END; INC(ip); END; END Do; END MyTest.Do DevCompiler.CompileThis MyTest!! DevDebug.UnloadThis MyTest Вроде кажется что это должно быть существенно быстрее... но нет. Чуда не произошло. Этот код выполняется чуть меньше 4 сек (всего на 2 сек. быстрее) Что я делаю не так? Я хочу добиться как минимум 2 секунд, т.к. на C аналогичный код (O1) выполняется менее 1.8 сек, а на Go на интерфейсах! 2.5 сек. |
Автор: | Comdiv [ Вторник, 21 Май, 2019 22:13 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Разве в других случаях Blackbox отстаёт не в те же 3-4 раза? |
Автор: | ilovb [ Вторник, 21 Май, 2019 22:21 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Ну обычно мне примерно понятно почему отстает. А сейчас вообще нет идей что за магия может быть в оптимизаторах C и Go, которая позволяет им в данном случае быть как минимум в 2 раза быстрее. Ну и кроме того мы наверно можем руками подобные оптимизации сделать, нет? Кстати я только что смог сократить до 3 сек. заменив это: Код: |Get: reg[b2] := mem[b4]; |Set: mem[b4] := reg[b2]; на это: Код: |Get: reg[b2].type := mem[b4].type; reg[b2].data.r := mem[b4].data.r; |Set: mem[b4].type := reg[b2].type; mem[b4].data.r := reg[b2].data.r; Что-то не врубаюсь почему это быстрее. ps Люди, а как размещать untagged? Я не могу с union безопасные указатели использовать? |
Автор: | Trurl [ Вторник, 21 Май, 2019 23:18 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Comdiv писал(а): Разве в других случаях Blackbox отстаёт не в те же 3-4 раза? Обычно раза в 2 от си и на одном уровне с го. |
Автор: | ilovb [ Среда, 22 Май, 2019 00:38 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Ну нее. В 2 раза медленнее Go - куда ближе к правде. Все таки я сейчас замеры в BB делаю при отключенных проверках. А вот кстати с высокоуровневым кодом на C# примерно похоже (4.5 сек против 6 сек. в BB): Код: using System; namespace core_hello { class Program { public static class OPER { public const int ADD = 0; public const int SET = 1; public const int GET = 2; public const int JMP = 3; public const int JGT = 4; public const int END = 5; }; public interface Value { void Set(Value v); void SetReal(double r); double Num(); } class Number: Value { double n; public void Set(Value v) { n = v.Num(); } public void SetReal(double r) { n = r; } public double Num() { return n; } public Number(double x) { n = x; } } static int emit1(byte b1, byte b2, byte b3, byte b4) { return (b1<<24) + (b2<<16) + (b3<<8) + b4; } static int emit2(byte b1, byte b2, byte b4) { return (b1<<24) + (b2<<16) + b4; } static int emit3(byte b1, byte b4) { return (b1<<24) + b4; } static int emit4(byte b1) { return b1<<24; } static void Main(string[] args) { int[] ops = new int[13]; ops[0] = emit2(OPER.GET, 0, 0); // 0 ops[1] = emit2(OPER.SET, 0, 10); // x = 0 ops[2] = emit2(OPER.GET, 0, 1); // 1 ops[3] = emit2(OPER.SET, 0, 11); // y = 1 ops[4] = emit2(OPER.GET, 0, 10); // x ops[5] = emit2(OPER.GET, 1, 2); // 100000000 ops[6] = emit3(OPER.JGT, 11); // if x > 100000000 goto ops[7] = emit2(OPER.GET, 0, 10); // x ops[8] = emit2(OPER.GET, 1, 11); // y ops[9] = emit1(OPER.ADD, 0, 0, 1); // _ = x + y ops[10] = emit2(OPER.SET, 0, 10); // x = _ ops[11] = emit3(OPER.JMP, 3); // goto ops[12] = emit4(OPER.END); // end Value[] reg = new Value[256]; Value[] mem = new Value[1000]; for(int i = 0; i < reg.Length; i++) { reg[i] = new Number(0); } for(int i = 0; i < mem.Length; i++) { mem[i] = new Number(0); } mem[0] = new Number(0); mem[1] = new Number(1); mem[2] = new Number(100000000); int ip = 0; for (;;) { int w = ops[ip]; int b4 = w & 0xFF; int b3 = (w >> 8) & 0xFF; int b2 = (w >> 16) & 0xFF; int b1 = (w >> 24) & 0xFF; switch (b1) { case OPER.GET: reg[b2].Set(mem[b4]); break; case OPER.SET: mem[b4].Set(reg[b2]); break; case OPER.ADD: reg[b2].SetReal(reg[b3].Num() + reg[b4].Num()); break; case OPER.JMP: ip = b4; break; case OPER.JGT: if (reg[0].Num() > reg[1].Num()) { ip = b4; } break; case OPER.END: goto endloop; } ip++; } endloop: Console.WriteLine(mem[10].Num()); } } } Правда я c# готовить не умею. Впрочем видимо как и BB. |
Автор: | SovietPony [ Среда, 22 Май, 2019 00:42 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Вооружайся дизассемблером. > Что-то не врубаюсь почему это быстрее. :| Значит на современных процессорах mov быстрее чем movsd. На 486 может быть было быстрее. |
Автор: | ilovb [ Среда, 22 Май, 2019 02:28 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Java всех порвала в клочья. Высокоуровневый код (аналогично C# и первому варианту BB) выполняется 1.6 сек (практически вровень с сишным низкоуровневым кодом с оптимизацией O3) Код: class HelloWorld {
public static class OPER { public static final int ADD = 0; public static final int SET = 1; public static final int GET = 2; public static final int JMP = 3; public static final int JGT = 4; public static final int END = 5; }; public interface Value { void Set(Value v); void SetReal(double r); double Num(); } static class Number implements Value { double n; public void Set(Value v) { n = v.Num(); } public void SetReal(double r) { n = r; } public double Num() { return n; } public Number(double x) { n = x; } } static int emit1(int b1, int b2, int b3, int b4) { return (b1<<24) + (b2<<16) + (b3<<8) + b4; } static int emit2(int b1, int b2, int b4) { return (b1<<24) + (b2<<16) + b4; } static int emit3(int b1, int b4) { return (b1<<24) + b4; } static int emit4(int b1) { return b1<<24; } public static void main(String[] args) { int[] ops = new int[13]; ops[0] = emit2(OPER.GET, 0, 0); // 0 ops[1] = emit2(OPER.SET, 0, 10); // x = 0 ops[2] = emit2(OPER.GET, 0, 1); // 1 ops[3] = emit2(OPER.SET, 0, 11); // y = 1 ops[4] = emit2(OPER.GET, 0, 10); // x ops[5] = emit2(OPER.GET, 1, 2); // 100000000 ops[6] = emit3(OPER.JGT, 11); // if x > 100000000 goto ops[7] = emit2(OPER.GET, 0, 10); // x ops[8] = emit2(OPER.GET, 1, 11); // y ops[9] = emit1(OPER.ADD, 0, 0, 1); // _ = x + y ops[10] = emit2(OPER.SET, 0, 10); // x = _ ops[11] = emit3(OPER.JMP, 3); // goto ops[12] = emit4(OPER.END); // end Value[] reg = new Value[256]; Value[] mem = new Value[1000]; for(int i = 0; i < reg.length; i++) { reg[i] = new Number(0); } for(int i = 0; i < mem.length; i++) { mem[i] = new Number(0); } mem[0] = new Number(0); mem[1] = new Number(1); mem[2] = new Number(100000000); int ip = 0; loop: for (;;) { int w = ops[ip]; int b4 = w & 0xFF; int b3 = (w >> 8) & 0xFF; int b2 = (w >> 16) & 0xFF; int b1 = (w >> 24) & 0xFF; switch (b1) { case OPER.GET: reg[b2].Set(mem[b4]); break; case OPER.SET: mem[b4].Set(reg[b2]); break; case OPER.ADD: reg[b2].SetReal(reg[b3].Num() + reg[b4].Num()); break; case OPER.JMP: ip = b4; break; case OPER.JGT: if (reg[0].Num() > reg[1].Num()) { ip = b4; } break; case OPER.END: break loop; } ip++; } System.out.println(mem[10].Num()); } } |
Автор: | Kemet [ Среда, 22 Май, 2019 10:55 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
А что будет, если CASE заменить на IF ELSIF? |
Автор: | ilovb [ Среда, 22 Май, 2019 11:26 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Я это проверял. Вроде не давало эффекта. |
Автор: | Kemet [ Среда, 22 Май, 2019 11:53 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Это хорошо, потому-что компилятор Активного Оберона чего-то там мудрит, и IF ELSIF в данном случае работает быстрее. А если ветки упорядочить в соответствии с Add = 0; Set = 1; Get = 2; Jmp = 3; Jgt = 4; End = 5; То тоже быстрее. Но это всё хоть и печально, но на фоне того, что на выполнение MyTest.Do у меня тратится 13 секунд ... |
Автор: | vvmtutby [ Среда, 22 Май, 2019 14:48 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Не будет ли интересней поработать с результатом компиляции данного варианта DSL кода: Код: x = 0
y = 0.1 while x <= 10000000000000000 do x = x + y end |
Автор: | Пётр Кушнир [ Среда, 22 Май, 2019 17:44 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Пооптимизируй компилер ББ. Всё станет намного быстрее. |
Автор: | ilovb [ Среда, 22 Май, 2019 22:58 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Заглянул в шикарный профилировщик Go и увидел что из 2.5 секунд целая секундища уходит на одну строчку. Понял что компилятор не так умен при работе с интерфейсами как я ожидал. В итоге 1.4 секунды. |
Автор: | ilovb [ Вторник, 28 Май, 2019 00:14 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Крутил вертел на досуге и так и сяк и не смог выжать из ББ ничего быстрее 3 секунд. Мне кажется это как-то связано с 32 битностью ББ. Скорее всего имеют место какие-то накладные расходы при выполнении на 64 разрядном процессоре и ОС. |
Автор: | SovietPony [ Вторник, 28 Май, 2019 07:10 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Так ты уже сравнил выхлопы дизассемблеров и нашёл 10 отличий? Да и проблема скорей не в 32-битности, а в оптимизациях под современные архитектруры. Это дело не интуитивное, но "растактовки" под современные процессоры тоже существуют. |
Автор: | ilovb [ Вторник, 28 Май, 2019 11:28 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Каким образом я могу посмотреть ассемблерный код? Да и какого конкретно куска кода? Ты же понимаешь что в Java, C# и Go нет ничего близкого BB во внутренней кухне. На Java в эти 1.6 секунды входит даже компиляция на лету. Что тут сравнивать можно? Кроме того, если там какая-то оптимизация под конвейер процессора, то я все равно это по ассемблеру не пойму. И оптимизация фиг знает какая тут может быть. SSE некуда притулить как кажется. Разворачивать цикл на BB я пробовал, это не помогло. Замена REAL на INTEGER тоже не помогает. Я пробовал заменить ветки CASE на вызовы процедур и профилировать. Складывается ощущение что оно просто тупо медленно на всем. Будто машинный код в BB в целом неторопливо выполняется. Попробую еще Go в 32 бита скомпилить. Может быть тоже медленнее будет. |
Автор: | Илья Ермаков [ Вторник, 28 Май, 2019 12:57 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Для ББ для машинного кода используй DevDecoder. https://oberoncore.ru/bbcc/subs/start |
Автор: | ilovb [ Вторник, 28 Май, 2019 20:21 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Такс, ну примерно начинаю понимать почему Go быстрее: https://i.imgur.com/f2hRPmL.png Оптимизирующий компилятор скорее всего выдаст более эффективный код просто сократив обмены с памятью. ps Ассемблерный выхлоп Go смотрел, но там пипец ничего не понятно. |
Автор: | ilovb [ Пятница, 31 Май, 2019 01:10 ] |
Заголовок сообщения: | Re: Виртуальная машина на CP |
Запустил код CP на gpcp для JVM. Скорость совсем немного меньше чем у решения на Java. 1.7 сек. ps Да, кстати, использование в Java абстрактного класса вместо интерфейса ускоряет ее до 1.45 сек., что идентично Go (и как не странно быстрее C -O3) |
Страница 1 из 3 | Часовой пояс: UTC + 3 часа |
Powered by phpBB® Forum Software © phpBB Group https://www.phpbb.com/ |