LINUX.ORG.RU

Время вызова FFI функций из различных ЯП

 , , , ,


0

3

Привет, раз уж неделя лиспа и ФП на ЛОРе, то задамся вопросом.

В каком языке (рантайме) время вызова FFI функций максимальное? Минимальное?

inb4: тривиальные случае C -> C, C++ -> C не интересуют.

Посто проаффилирован письмом некого чувака в рассылку racket: (время доступа из racket - 150нс, из C - 3 нс)

One of important aspects for me is efficiency of Foreign Function Interface. Unfortunately, it seems that FFI is quite slow.

Here is the code I have:

-- test.c --

void do_test(void)
{}

-- test.rkt --

#lang racket/base

(require
    ffi/unsafe
    ffi/unsafe/define)

(define-ffi-definer define-t (ffi-lib "libtest"))

(define-t do_test (_fun -> _void))

(define (do_benchmark)
    (time (for ([i (in-range 1000000)])
        (do_test)))
)

(for ([i (in-range 10)])
    (do_benchmark))

-- Makefile --

libtest.so: test.o
    gcc -fPIC -shared -pthread -o libtest.so test.o

test.o: test.c
    gcc test.c -fPIC -shared -pthread -c -O2 -o test.o

clean:
    rm -f test.o libtest.so

Running the test suggests that a call to «do_test» costs about 150 nanoseconds. I would expect something not larger than 5 nanoseconds. A test where C program calls this function shows the call costs 3 nanoseconds.

P.S Увидел, что там Matthew Flatt ответил, что срезал треть, и 5x - это текущий потолок, дальше - только лезть в jit.

I haven't particularly tried to make foreign calls go faster, so I expect there's room for improvement. A quick profile suggested an easy way to trim 1/3 of the time, so I've done that (pushed to the git repo).

In my profile, 15-20% of the time is spent in libffi's wrappers, though, so a 5x improvement is probably an upper bound on the current design --- leaving still a 10x difference between a direct C call and Racket-to-C call. To do better, we might be able to use the JIT infrastructure to generate more direct calls for simple function types, but I'm not sure how general we can make that.

★★★★★

Последнее исправление: x4DA (всего исправлений: 3)

Ответ на: комментарий от x4DA

Ну да, это я и написал то письмо в рассылку.

В отличии от Matthew Flatt я с racket знаком очень поверхностно.

Полагаю, что на каждый вызов процедуры racket делает массу работы типа проверок типов входных данных, поиска адреса процедуры и т.д. Но что именно он делает - мне неведомо.

В идеале, если бы racket бы по-настоящему типизирован (а такое ощущение, что на этапе компиляции происходит type erasure), то FFI был бы очень быстрым (т.е. где-то порядка 3-4 наносекунд на функцию из моего примера).

Про другие языки могу написать позднее, если вам действительно интересно. Но в длинную дискуссию мне ввязываться не охота. Мне работу делать надо. :-)

rtvd ★★★★★
()
Последнее исправление: rtvd (всего исправлений: 1)

Как правило, функции, дергающиеся через FFI, выполняют работу, на фоне которой 150 нс незаметны.

NikolaSh
()

Алсо, в Racket уже можно нормально вызывать внешние variadic-функции?

buddhist ★★★★★
()
Ответ на: комментарий от NikolaSh

Как правило, функции, дергающиеся через FFI, выполняют работу, на фоне которой 150 нс незаметны.

Вообще всё началось с того, что я захотел из racket дёрнуть clock_gettime. Выполнение этой функции на моём компьютере занимает 24 наносекунды.

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

Так что не обобщайте, пожалуйста.

rtvd ★★★★★
()
Ответ на: комментарий от x4DA

А что именно интересно?

Я пробовал FFI в Java, Ocaml (но очень давно), SBCL, GHC.

Виды FFI можно разделить на обёрточные и прямые.

* «Прямые» могут вызывать native функции с почти произвольной сигнатурой и native код не обязан при этом знать, что его будут вызывать из другого языка.

Примеры: SBCL и GHC

Это, правда, не значит, что вызвать можно что угодно. Например, из GHC нельзя вызвать функцию, что возвращает структуру по значению.

* «Обёрточные» предоставляют библиотеку «обёрток», в которые нужно заворачивать значения, чтобы передать их через FFI. Соответственно, native код должен иметь соответствующие сигнатуры и подключать необходимые библиотеки.

Примеры: Java, Ocaml.

Чисто в теории, «прямые» FFI будут быстрее и способнее чем «обёрточные». На практике это не всегда так.

GHC FFI на моих тестах был медленней чем Java JNI. Причина была в том, что GHC FFI был вынужден делать больше ненужной работы чем Sun Java JNI (очень любит выделять память в куче, хотя можно было для целей FFI выделить фиксированный блок или использова стек). И хотя я немного попатчил его и кое-что стало работать быстрей, всё равно GHC FFI далек от идеала в моём представлении.

Про Ocaml FFI у меня ярких воспоминаний не осталось. Про SBCL FFI помню что он был не особо быстрым, но по крайней мере «прямым» и не нужно было писать boilerplate чтобы вызвать что нужно.

rtvd ★★★★★
()

stones situthom

test.rkt

Сколько ненужных буквочек, скобочек и сущностей!!!

(do 10 (bench (do 1000000 (native "./libtest.so" "do_test"]
0.023 sec
0.020 sec
0.020 sec
0.020 sec
0.020 sec
0.020 sec
0.020 sec
0.020 sec
0.020 sec
0.020 sec
-> NIL
anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.