Pull to refresh

Простой тест llvm/libjit часть II, те же + gnu lightning

Reading time 6 min
Views 1.2K
Эта статья является продолжением Простой тест libjit vs llvm, где сравнивалось быстродействие библиотек для Just In Time компиляции llvm и libjit на примере задачи решета эратосфена.

В этой статье решается точно та же задача — решето эратосфена при помощи еще одной JIT библиотеки — GNU lightning .

Пару слов о gnu lightning — на самом деле это вовсе не библиотека, а набор макросов, которые формируют исполняемый буфер.

Итак, то же решение при помощи

GNU Lightning



001:  #include <stdio.h>
002:  #include <lightning.h>
003:  #include <math.h>
004:  #include <string.h>
005:  

006:  static jit_insn codeErato[1024];
007:  static jit_insn codemain[1024];
008:  
009:  typedef int (*pifi)(int); /* Pointer to Int Function of Int */
010:  typedef void (*pvfv)(void); /* Pointer to void Function of void */
011:  char a[100000];
012:  
013:  int main()
014:  {
015:      pifi erato = (pifi) (jit_set_ip(codeErato).iptr);
016:      pvfv func_main;
017:      int n, ii;
018:      jit_insn *for_cond, *for_end, *end_if, *while_cond, *while_end,
019:      *main_for_beg, *main_for_end;
020:  
021:      jit_prolog(1); 
022:      n = jit_arg_i();          /* n = arg_i  */
023:  
024:      jit_getarg_ui(JIT_V0, n); /*  V0 = n   */
025:      jit_extr_i_f(JIT_FPR0, JIT_V0);
026:      jit_prepare(1);
027:      jit_pusharg_f( JIT_FPR0);
028:      jit_finish( sqrtf);
029:      jit_retval(JIT_FPR0);
030:      jit_roundr_f_i(JIT_V1, JIT_FPR0); /*  V1 = q */
031:  
032:      jit_movi_p(JIT_V2, a);           /* V2 = a*/
033:      jit_movi_i(JIT_R0, 1);
034:      jit_prepare(3);
035:      jit_pusharg_i(JIT_V0);        /* size_t n */
036:      jit_pusharg_i(JIT_R0);        /* int c - fillcahr */
037:      jit_pusharg_p(JIT_V2);        /* array to fill */
038:      jit_finish(memset);
039:  
040:      jit_movi_ui(JIT_R0, 2);       /* i = 1 (R0)*/
041:      for_cond = jit_get_label();
042:      for_end = jit_bgtr_ui(jit_forward(), JIT_R0, JIT_V1);
043:      jit_ldxr_c(JIT_R2, JIT_V2, JIT_R0);
044:      end_if = jit_beqi_ui(jit_forward(), JIT_R2, 0);
045:      jit_mulr_ui(JIT_R1, JIT_R0, JIT_R0); /* j = R1 */
046:      while_cond = jit_get_label();
047:      while_end = jit_bgtr_ui(jit_forward(), JIT_R1, JIT_V0);
048:      jit_stxr_c(JIT_V2, JIT_R1, 0);
049:      jit_addr_ui(JIT_R1, JIT_R1, JIT_R0);
050:      jit_jmpi(while_cond);
051:      jit_patch(while_end);
052:      jit_patch(end_if);
053:      jit_addi_ui(JIT_R0, JIT_R0, 1);
054:      jit_jmpi(for_cond);
055:      jit_patch(for_end);
056:  
057:      jit_movr_ui(JIT_RET, JIT_R1);
058:      jit_ret(); 
059:      jit_flush_code(codeErato, jit_get_ip().ptr);
060:  
061:      func_main = (pvfv) (jit_set_ip(codemain).iptr);
062:      jit_prolog(0);
063:      jit_movi_ui(JIT_V0, 1);
064:      main_for_beg = jit_get_label();
065:      main_for_end = jit_bgti_ui(jit_forward(), JIT_V0, 100000 );
066:      jit_movi_ui(JIT_R0, 50000);
067:      jit_prepare(1);
068:      jit_pusharg_ui( JIT_R0);
069:      jit_finish(erato);
070:      jit_addi_ui(JIT_V0,JIT_V0,1);
071:      jit_jmpi(main_for_beg);
072:      jit_patch(main_for_end);
073:      jit_ret();
074:      jit_flush_code(codemain, jit_get_ip().ptr);
075:  
076:      puts("Go");
077:      func_main();
078:      return 0;
079:  }
080:  


Компилируем и запускаем:
$ gcc lightest.c -lm -o lightest
$ for i in `seq 1 10`; do /usr/bin/time -f '%U' ./lightest ; done
32.80
32.59
32.57
32.55
32.53
32.59
32.53
32.69
32.54
32.53


Итого среднее время выполнения 32.59

Обьединяя результаты с теми, что были получены в предыдущей статье:

JIT библиотека Время выполнения в секундах, (меньше=лучше)
LLVM 13.77
LIBJIT 14.17
GNU LIGHTNING 32.59
И просто для информации:
gcc -O0 50.09
gcc -O1 13.79


То есть, результаты gnu lightning по скорострельности находятся примерно между скомпилированной С программой «без» и «с» оптимизацией.
Надо сказать, что gnu lighning представляет интерфекйс намного более низкого уровня чем llvm и libjit. Многие вещи остаются на совести программиста. Например — распределение регистров (lightning не делает этого, просто есть 6 регистров для int и 6 для float/double, и крутись как хочешь). Например — оптимизация.

Из неприятных вещей, я бы также отметил то, что для lightinig до трансляции нужно самому выделять буфер под код. Что может быть проблемой, так как необходимый размер этого буфера можно оценить только приблизительно, и есть риск словить segmentation fault, если ошибешься. Кроме того, документация на сайте неточна. К примеру, в ней используется jit_allocai(), которой в текущей версии lightning уже нет.

Из положительных — система команд в API в lightning неплохо продумана, поэтому если сравнивать по многословности — видно, что решение на lightning практически вдвое короче чем на libjit или llvm. Поскольку весь lightning — это просто набор макросов, получаются программы, которые не зависят ни от каких сторонних библиотек. Размер результирующего исполяемого файла получается очень скромный (это камешек в огород llvm, где размер файла достаточно большой).

Мораль.


GNU lightning я бы стал использовать, только если результирующая программа должна работать в очень жестких условиях по памяти и месту на диске. Во всех остальных случаях — libjit или llvm обьективно лучше и безопаснее.
Tags:
Hubs:
+6
Comments 2
Comments Comments 2

Articles