Вопросы производительности Haskell FFI / C? - PullRequest
28 голосов
/ 14 апреля 2011

Если вы используете Haskell в качестве библиотеки , которую называют из моей C-программы, как это отразится на производительности при выполнении вызовов?Например, если у меня есть набор данных проблемного мира, скажем, размером 20 КБ, и я хочу запустить что-то вроде:

// Go through my 1000 actors and have them make a decision based on
// HaskellCode() function, which is compiled Haskell I'm accessing through
// the FFI.  As an argument, send in the SAME 20kB of data to EACH of these
// function calls, and some actor specific data
// The 20kB constant data defines the environment and the actor specific
// data could be their personality or state
for(i = 0; i < 1000; i++)
   actor[i].decision = HaskellCode(20kB of data here, actor[i].personality);

Что здесь произойдет - возможно ли будет сохранить это?20 КБ данных как глобальная неизменяемая ссылка где-то, к которой обращается код на Haskell, или я должен каждый раз создавать копию этих данных?

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

Кроме того, я хотел бы распараллелить это, как dispatch_apply() GCD или Parallel.ForEach (..) C #.Мое обоснование для распараллеливания за пределами Haskell заключается в том, что я знаю, что всегда буду работать со многими отдельными вызовами функций, то есть с 1000 действующими лицами, поэтому использование мелкозернистого распараллеливания внутри функции Haskell не лучше, чем управление ею на уровне C.Является ли запущенный экземпляр FFI Haskell «Безопасным для потока» и как мне этого добиться - нужно ли инициализировать экземпляр Haskell при каждом запуске параллельного запуска?(Кажется, медленно, если я должен ..) Как мне добиться этого с хорошей производительностью?

Ответы [ 4 ]

20 голосов
/ 14 апреля 2011

как это влияет на производительность при совершении звонков на него

Предполагая, что вы запускаете среду выполнения Haskell только один раз ( как этот ) на моей машине,выполнение вызова функции из C в Haskell, передача Int вперед и назад через границу, занимает около 80 000 циклов ( 31 000 нс на моем Core 2) - определяется экспериментально через rdstc register

Могу ли я сохранить эти 20 КБ данных в качестве глобальной неизменяемой ссылки где-нибудь, к которой обращается код Haskell

Да, это, конечно, возможно.Если данные действительно неизменны, то вы получите тот же результат, если вы:

  • продвигаете данные вперед и назад через границу языка путем маршаллинга;
  • передаете ссылку на данныетуда-сюда;
  • или кэшируйте его в IORef на стороне Haskell.

Какая стратегия лучше?Это зависит от типа данных.Наиболее идиоматичным способом было бы передавать ссылку на данные C взад-вперед, обрабатывая ее как ByteString или Vector на стороне Haskell.

Я бы хотел распараллелить это

Я бы настоятельно рекомендовал бы тогда инвертировать элемент управления и выполнить распараллеливание из среды выполнения Haskell - это будет намного более устойчивым, поскольку этот путь был тщательно протестирован.

Что касается безопасности потоков, очевидно, безопасно выполнять параллельные вызовы функций foreign exported, работающих в одной и той же среде выполнения, хотя и вполне уверен, что никто не пытался сделать это для получения параллелизма.Вызовы получают возможность, которая по сути является блокировкой, поэтому множественные вызовы могут блокироваться, уменьшая ваши шансы на параллелизм.В случае многоядерности (например, -N4 или около того) ваши результаты могут отличаться (доступно несколько возможностей), однако это почти наверняка плохой способ повысить производительность.

Опять же, много параллельных вызовов функцийиз Haskell через forkIO - это более хорошо документированный, лучше протестированный путь, с меньшими накладными расходами, чем работа на стороне C, и, возможно, с меньшим количеством кода в конце.

Просто вызовите функцию Haskell,это, в свою очередь, приведет к параллелизму через множество потоков на Haskell.Легко!

9 голосов
/ 15 апреля 2011

Я использую смесь потоков C и Haskell для одного из своих приложений и не заметил значительного снижения производительности, переключаясь между ними. Поэтому я создал простой тест ... который немного быстрее / дешевле, чем у Дона. Это измеряет 10 миллионов итераций на 2,66 ГГц i7:

$ ./foo
IO  : 2381952795 nanoseconds total, 238.195279 nanoseconds per, 160000000 value
Pure: 2188546976 nanoseconds total, 218.854698 nanoseconds per, 160000000 value

Скомпилировано с GHC 7.0.3 / x86_64 и gcc-4.2.1 на OSX 10.6

ghc -no-hs-main -lstdc++ -O2 -optc-O2 -o foo ForeignExportCost.hs Driver.cpp

Haskell:

{-# LANGUAGE ForeignFunctionInterface #-}

module ForeignExportCost where

import Foreign.C.Types

foreign export ccall simpleFunction :: CInt -> CInt
simpleFunction i = i * i

foreign export ccall simpleFunctionIO :: CInt -> IO CInt
simpleFunctionIO i = return (i * i)

И приложение OSX C ++ для его управления должно быть легко адаптировано к Windows или Linux:

#include <stdio.h>
#include <mach/mach_time.h>
#include <mach/kern_return.h>
#include <HsFFI.h>
#include "ForeignExportCost_stub.h"

static const int s_loop = 10000000;

int main(int argc, char** argv) {
    hs_init(&argc, &argv);

    struct mach_timebase_info timebase_info = { };
    kern_return_t err;
    err = mach_timebase_info(&timebase_info);
    if (err != KERN_SUCCESS) {
        fprintf(stderr, "error: %x\n", err);
        return err;
    }

    // timing a function in IO
    uint64_t start = mach_absolute_time();
    HsInt32 val = 0;
    for (int i = 0; i < s_loop; ++i) {
        val += simpleFunctionIO(4);
    }

    // in nanoseconds per http://developer.apple.com/library/mac/#qa/qa1398/_index.html
    uint64_t duration = (mach_absolute_time() - start) * timebase_info.numer / timebase_info.denom;
    double duration_per = static_cast<double>(duration) / s_loop;
    printf("IO  : %lld nanoseconds total, %f nanoseconds per, %d value\n", duration, duration_per, val);

    // run the loop again with a pure function
    start = mach_absolute_time();
    val = 0;
    for (int i = 0; i < s_loop; ++i) {
        val += simpleFunction(4);
    }

    duration = (mach_absolute_time() - start) * timebase_info.numer / timebase_info.denom;
    duration_per = static_cast<double>(duration) / s_loop;
    printf("Pure: %lld nanoseconds total, %f nanoseconds per, %d value\n", duration, duration_per, val);

    hs_exit();
}
1 голос
/ 14 апреля 2011

Отказ от ответственности: у меня нет опыта работы с FFI.

Но мне кажется, что если вы хотите повторно использовать 20 Кбайт данных, чтобы не передавать их каждый раз, то вы могли бы просто иметь метод, который принимает список «личностей» и возвращает список "решение".

Так что, если у вас есть функция

f :: LotsaData -> Personality -> Decision
f data p = ...

Тогда почему бы не сделать вспомогательную функцию

helper :: LotsaData -> [Personality] -> [Decision]
helper data ps = map (f data) ps

И ссылаться на это? Используя этот способ, тем не менее, если вы хотите распараллелить, вам нужно сделать это на стороне Хаскеля с параллельными списками и параллельным отображением.

Я полагаюсь на экспертов, чтобы объяснить, можно ли / как массивы C можно легко маршалировать в списки Haskell (или аналогичную структуру).

...