Эффективный способ выполнения n-арных ветвей/табличных функций?

Я пытаюсь получить некоторую базовую информацию о характеристиках производительности веток в SBV.

Предположим, у меня есть SInt16 и очень разреженная таблица поиска Map Int16 a. Я могу реализовать поиск с вложенным ite:

sCase :: (Mergeable a) => SInt16 -> a -> Map Int16 a -> a
sCase x def = go . toList
  where
    go [] = def
    go ((k,v):kvs) = ite (x .== literal k) v (go kvs)

Однако это означает, что сгенерированное дерево будет очень глубоким.

  1. Это имеет значение?
  2. Если да, лучше ли вместо этого создать сбалансированное дерево ветвей, эффективно отражающее структуру Map? Или есть какая-то другая схема, которая давала бы еще лучшую производительность?
  3. Если на карте менее 256 записей, будет ли что-нибудь меняться, чтобы сжать ее так, чтобы sCase работал на SInt8 и Map Int8 a?
  4. Есть ли какой-нибудь встроенный комбинатор SBV для этого варианта использования, который работает лучше, чем повторяющийся ite?

РЕДАКТИРОВАНИЕ. Оказывается, очень важно, какой у меня a, поэтому позвольте мне добавить к этому некоторые подробности. В настоящее время я использую sCase для ветвления в вычислении с отслеживанием состояния, смоделированном как RWS r w s a, со следующими экземплярами:

instance forall a. Mergeable a => Mergeable (Identity a) where
    symbolicMerge force cond thn els = Identity $ symbolicMerge force cond (runIdentity thn) (runIdentity els)

instance (Mergeable s, Mergeable w, Mergeable a, forall a. Mergeable a => Mergeable (m a)) => Mergeable (RWST r w s m a) where
        symbolicMerge force cond thn els = Lazy.RWST $
            symbolicMerge force cond (runRWST thn) (runRWST els)

Итак, убрав все newtypes, я хотел бы перейти к чему-то типа r -> s -> (a, s, w) s.t. Mergeable s, Mergeable w и Mergeable a.


person Cactus    schedule 08.07.2020    source источник


Ответы (1)


Символические поиски стоят дорого

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

Массивы SMTLib

Однако лучшим решением в этих случаях является использование поддержки SMT для массивов: http://smtlib.cs.uiowa.edu/theories-ArraysEx.shtml

Массивы SMTLib отличаются от того, что вы считаете массивом в обычном языке программирования: он не имеет границ. В этом смысле это скорее карта от входов к выходам, охватывающая всю область. (т. е. они эквивалентны функциям.) Но у SMT есть специальные теории для работы с массивами, и поэтому они могут гораздо эффективнее решать проблемы, связанные с массивами. (С другой стороны, нет понятия индекса за пределами границ или какого-либо контроля диапазона элементов, к которым вы можете получить доступ. Однако вы можете кодировать их самостоятельно поверх абстракции, оставляя вам решать, как вы хотите обрабатывать такие недопустимые обращения.)

Если вам интересно узнать больше о том, как решатели SMT работают с массивами, классический справочник: http://theory.stanford.edu/%7Earbrad/papers/arrays.pdf

Массивы в SBV

SBV поддерживает массивы через класс SymArray: https://hackage.haskell.org/package/sbv-8.7/docs/Data-SBV.html#t:SymArray

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

Преобразование карты Haskell в массив SBV

Возвращаясь к вашему первоначальному вопросу, у меня возникнет соблазн использовать SArray для моделирования такого поиска. Я бы закодировал это как:

{-# LANGUAGE ScopedTypeVariables #-}

import Data.SBV
import qualified Data.Map as M
import Data.Int

-- Fill an SBV array from a map
mapToSArray :: (SymArray array, SymVal a, SymVal b) => M.Map a (SBV b) -> array a b -> array a b
mapToSArray m a = foldl (\arr (k, v) -> writeArray arr (literal k) v) a (M.toList m)

И используйте его как:

g :: Symbolic SBool
g = do let def = 0

       -- get a symbolic array, initialized with def
       arr <- newArray "myArray" (Just def)

       let m :: M.Map Int16 SInt16
           m = M.fromList [(5, 2), (10, 5)]

       -- Fill the array from the map
       let arr' :: SArray Int16 Int16 = mapToSArray m arr

       -- A simple problem:
       idx1 <- free "idx1"
       idx2 <- free "idx2"

       pure $ 2 * readArray arr' idx1 + 1 .== readArray arr' idx2

Когда я запускаю это, я получаю:

*Main> sat g
Satisfiable. Model:
  idx1 =  5 :: Int16
  idx2 = 10 :: Int16

Вы можете запустить его как satWith z3{verbose=True} g, чтобы увидеть генерируемые им выходные данные SMTLib, что позволяет избежать дорогостоящих поисков, просто делегируя эти задачи серверному решателю.

Эффективность

Вопрос о том, будет ли это эффективным, действительно зависит от того, сколько элементов имеет ваша карта, из которой вы строите массив. Чем больше количество элементов и чем сложнее ограничения, тем менее эффективным он будет. В частности, если вы когда-нибудь будете писать в индекс, который является символическим, я ожидаю замедления времени решения. Если они все константы, это должно быть относительно производительным. Как это обычно бывает в символическом программировании, очень трудно предсказать какую-либо производительность, не видя реальной проблемы и не экспериментируя с ней.

Массивы в контексте запроса

Функция newArray работает в символьном контексте. Если вы находитесь в контексте запроса, вместо этого используйте freshArray: https://hackage.haskell.org/package/sbv-8.7/docs/Data-SBV-Control.html#v:freshArray

person alias    schedule 08.07.2020
comment
Ничего себе, это полная противоположность моей наивной интуиции: я бы подумал, что наличие статически известной структуры символьных значений более эффективно, чем символическая структура. - person Cactus; 09.07.2020
comment
Мне непонятно, как превратить мой тип значения (который не имеет формы SBV v, это какой-то a такой, что Mergeable a) во что-то, что я могу поместить в SArray. - person Cactus; 09.07.2020
comment
Это эффективно, потому что у бэкэнд-решателя есть собственная теория. Обратите внимание, что проблема не в представлении, а в символическом поиске. (Если все ваши поиски выполняются по конкретным адресам, то вы все равно можете просто использовать исходную карту Haskell.) - person alias; 09.07.2020
comment
Более конкретно: как мне сделать SArray, который содержит SBV a -> SBV b функций? - person Cactus; 09.07.2020
comment
Не уверен насчет вашего второго комментария. Пожалуйста, опубликуйте код, который иллюстрирует трудности. Обратите внимание, что тип SBV a является фантомным в a, и все символьные типы имеют эту структуру. В противном случае вы можете использовать несколько параллельных структур для каждого компонента. - person alias; 09.07.2020
comment
Вы не можете создать массив с функциями в качестве аргументов. SMTLib представляет собой многосортированную логику функций первого порядка, поэтому такое символьное значение более высокого порядка не может храниться в массиве. - person alias; 09.07.2020
comment
Я разместил более подробную информацию. Имеет ли смысл запускать каждую ветвь и помещать полученные (a, s, w) тройки в массив? Хотя это приводит меня к проблеме необходимости запускать это в монаде Symbolic, где у меня нет удобного доступа к аргументам r и s. - person Cactus; 09.07.2020
comment
Вам придется каким-то образом привести его к структуре первого порядка. Просто нет другого способа использовать SMT-массив. Просто для ясности: вы будете индексировать эту структуру с помощью символического адреса, верно? В противном случае вы можете просто использовать карту Haskell. Все обсуждение основано на требовании, чтобы вы выполняли символический поиск. Если это так, вам придется сгладить структуру более высокого порядка и сохранить SBV в массиве. - person alias; 09.07.2020
comment
Конечно, да, я обращусь к нему символическим адресом. - person Cactus; 09.07.2020
comment
Отлично. Если вы хотите использовать SMT-массивы, вам нужно будет преобразовать то, что вы в них храните, в обычное символьное значение. Если это невозможно, вы можете использовать свою первоначальную идею просмотра обычного списка с помощью линейного обхода. Никакой другой метод не будет значительно лучше. - person alias; 09.07.2020
comment
Давайте продолжим это обсуждение в чате. - person Cactus; 09.07.2020