1

У меня есть приложение, которое написано полностью с использованием парадигмы FRP, и я думаю, что у меня возникают проблемы с производительностью из-за того, что я создаю потоки. Это написано в Haxe, но проблема не зависит от языка.Кэширование потоков в функциональном реактивном программировании

К примеру, у меня есть эта функция, которая возвращает поток, который решает каждый раз, когда конфигурационный файл обновляется для этого конкретного раздела, как следующее:

function getConfigSection(section:String) : Stream<Map<String, String>> { 
    return configFileUpdated() 
     .then(filterForSectionChanged(section)) 
     .then(readFile) 
     .then(parseYaml); 
} 

В реактивной библиотеке программирования Я использую называюсь promhx каждого шаг цепи должен помнить его последнее разрешенное значение, но я думаю, что каждый раз, когда я вызываю эту функцию, я воссоздаю поток и перерабатываю каждый шаг. Это проблема с тем, как я использую ее, а не библиотекой.

Поскольку эта функция называется повсеместной разборкой YAML каждый раз, когда она необходима, это приводит к гибели производительности и занимает более 50% времени процессора в соответствии с профилированием.

Как исправить я сделал что-то вроде следующего, используя карту, хранящуюся в переменной экземпляра, который кэширует потоки:

function getConfigSection(section:String) : Stream<Map<String, String>> { 
    var cachedStream = this._streamCache.get(section); 
    if (cachedStream != null) { 
     return cachedStream; 
    } 

    var stream = configFileUpdated() 
     .filter(sectionFilter(section)) 
     .then(readFile) 
     .then(parseYaml); 

    this._streamCache.set(section, stream); 
    return stream; 
} 

Это может быть хорошим решением проблемы, но он не чувствует себя хорошо мне. Мне интересно, может ли кто-нибудь подумать о более чистом решении, которое может использовать более функциональный подход (закрытие и т. Д.) Или даже расширение, которое я могу добавить в поток, как функция кеша.

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

Мне понравились бы любые идеи, которые могли бы дать мне свежую перспективу!

ответ

2

Ну, я думаю, что один ответ просто абстрагироваться от кэширования like so:

class Test { 
    static function main() { 
     var sideeffects = 0; 
     var cached = memoize(function (x) return x + sideeffects++); 
     cached(1); 
     trace(sideeffects);//1 
     cached(1); 
     trace(sideeffects);//1 
     cached(3); 
     trace(sideeffects);//2 
     cached(3); 
     trace(sideeffects);//2 
    } 
    @:generic static function memoize<In, Out>(f:In->Out):In->Out { 
     var m = new Map<In, Out>(); 
     return 
      function (input:In) 
       return switch m[input] { 
        case null: m[input] = f(input); 
        case output: output; 
       } 
    } 
} 

Вы можете быть в состоянии найти более «функциональной» реализацию memoize вниз по дороге. Но важно то, что сейчас это отдельная вещь, и вы можете использовать ее по своему усмотрению.

Вы можете выбрать memoize(parseYaml), так что переключение двух состояний в файле фактически становится очень дешевым после того, как оба были разобраны один раз. Вы также можете настроить memoize для управления размером кеша в соответствии с любой стратегией, которая окажется наиболее ценной.

+0

Это намного более чистый и шаг в правильном направлении. Спасибо за это! –