Подтвердить что ты не робот

Вопросы производительности Haskell FFI/C?

Если использовать 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(..) С#. Мое объяснение для распараллеливания вне Haskell заключается в том, что я знаю, что я всегда буду работать со многими отдельными вызовами функций, то есть 1000 участников, поэтому использование мелкозернистого распараллеливания внутри функции Haskell не лучше, чем управление им на уровне C. Является ли запуск экземпляров FFI Haskell "Thread Safe" и как мне это достичь - нужно ли инициализировать экземпляр Haskell каждый раз, когда начинаю параллельный запуск? (Кажется медленным, если я должен...) Как я могу достичь этого с хорошей производительностью?

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

Я использую сочетание потоков 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 С++ для его запуска должно быть простым в настройке для 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();
}

Ответ 4

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

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

Итак, если у вас есть функция

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

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

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

И вызывать это? Тем не менее, если вы хотите распараллелить, вам нужно будет сделать это с помощью Haskell с параллельными списками и параллельной картой.

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