Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweak cost of running concat #1269

Merged
merged 4 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/Pact/Gas/Table.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ data GasCostConfig = GasCostConfig
, _gasCostConfig_sortFactor :: Gas
, _gasCostConfig_distinctFactor :: Gas
, _gasCostConfig_concatenationFactor :: Gas
, _gasCostConfig_textConcatenationFactorOffset :: MilliGas -- up-front cost of text concatenation
, _gasCostConfig_textConcatenationFactorStringLen :: MilliGas -- additional cost of concatenation per character in the average string
, _gasCostConfig_textConcatenationFactorStrings :: MilliGas -- additional cost of concatenation per list item
, _gasCostConfig_moduleCost :: Gas
, _gasCostConfig_moduleMemberCost :: Gas
, _gasCostConfig_useModuleCost :: Gas
Expand All @@ -58,6 +61,9 @@ defaultGasConfig = GasCostConfig
, _gasCostConfig_sortFactor = 1
, _gasCostConfig_distinctFactor = 1
, _gasCostConfig_concatenationFactor = 1 -- TODO benchmark
, _gasCostConfig_textConcatenationFactorOffset = MilliGas 50000
, _gasCostConfig_textConcatenationFactorStringLen = MilliGas 20
, _gasCostConfig_textConcatenationFactorStrings = MilliGas 40
, _gasCostConfig_moduleCost = 1 -- TODO benchmark
, _gasCostConfig_moduleMemberCost = 1
, _gasCostConfig_useModuleCost = 1 -- TODO benchmark
Expand Down Expand Up @@ -248,10 +254,21 @@ tableGasModel gasConfig =
Just cs -> _gasCostConfig_selectColumnCost gasConfig * fromIntegral (length cs)
GSortFieldLookup n ->
gasToMilliGas $ fromIntegral n * _gasCostConfig_sortFactor gasConfig
GConcatenation i j ->
gasToMilliGas $ fromIntegral (i + j) * _gasCostConfig_concatenationFactor gasConfig
GFoldDB ->
gasToMilliGas $ _gasCostConfig_foldDBCost gasConfig
GConcatenation i j -> gasToMilliGas $
fromIntegral (i + j) * _gasCostConfig_concatenationFactor gasConfig
GTextConcatenation nChars nStrings ->
let
MilliGas offsetCost = _gasCostConfig_textConcatenationFactorOffset gasConfig
MilliGas charCost = _gasCostConfig_textConcatenationFactorStringLen gasConfig
MilliGas stringCost = _gasCostConfig_textConcatenationFactorStrings gasConfig

costForAverageStringLength =
(fromIntegral nChars * charCost) `div` fromIntegral nStrings
costForNumberOfStrings =
fromIntegral nStrings * stringCost
in
MilliGas $ offsetCost + costForAverageStringLength + costForNumberOfStrings
GFoldDB -> gasToMilliGas $ _gasCostConfig_foldDBCost gasConfig
GPostRead r -> gasToMilliGas $ case r of
ReadData cols -> _gasCostConfig_readColumnCost gasConfig * fromIntegral (Map.size (_objectMap $ _rdData cols))
ReadKey _rowKey -> _gasCostConfig_readColumnCost gasConfig
Expand Down
38 changes: 29 additions & 9 deletions src/Pact/Native.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1259,15 +1259,35 @@ identity _ [a'] = return a'
identity i as = argsError i as

concat' :: GasRNativeFun e
concat' i [TList ls _ _] = computeGas' i (GMakeList $ fromIntegral $ V.length ls) $ let
-- Use GMakeList because T.concat is O(n) on the number of strings in the list
ls' = V.toList ls
concatTextList = flip TLiteral def . LString . T.concat
in fmap concatTextList $ forM ls' $ \case
TLitString s -> return s
t -> isOffChainForkedError FlagDisablePact47 >>= \case
OffChainError -> evalError' i $ "concat: expecting list of strings: " <> pretty t
OnChainError -> evalError' i $ "concat: expected list of strings, received value of type: " <> pretty (typeof' t)
concat' i [TList ls _ _] = do
imalsogreg marked this conversation as resolved.
Show resolved Hide resolved

disablePact48 <- isExecutionFlagSet FlagDisablePact48
let concatGasCost =
if disablePact48
then
-- Prior to pact-4.8, gas cost is proportional to the number of
-- strings being concatenated.
GMakeList $ fromIntegral $ V.length ls
else
-- Beginning with pact-4.8, gas cost in proportional to the number of
-- characters being concatinated and the length of the list.
imalsogreg marked this conversation as resolved.
Show resolved Hide resolved
let nChars = sum $ termLen <$> ls
where
termLen t = case t of
TLitString s -> T.length s
_ -> 0
imalsogreg marked this conversation as resolved.
Show resolved Hide resolved
nStrings = V.length ls
in
GTextConcatenation nChars nStrings
computeGas' i concatGasCost $
let
ls' = V.toList ls
concatTextList = flip TLiteral def . LString . T.concat
in fmap concatTextList $ forM ls' $ \case
TLitString s -> return s
t -> isOffChainForkedError FlagDisablePact47 >>= \case
OffChainError -> evalError' i $ "concat: expecting list of strings: " <> pretty t
OnChainError -> evalError' i $ "concat: expected list of strings, received value of type: " <> pretty (typeof' t)
concat' i as = argsError i as

-- | Converts a string to a vector of single character strings
Expand Down
6 changes: 5 additions & 1 deletion src/Pact/Types/Gas.hs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ data GasArgs
| GSortFieldLookup !Int
-- ^ Cost of sorting by lookup fields
| GConcatenation !Int !Int
-- ^ Cost of concatenating two strings, lists, and objects
-- ^ Cost of concatenating two strings before pact 4.8, lists, and objects
| GTextConcatenation !Int !Int
-- ^ Cost of concatenating a list of strings with the given total character
-- count and list length after pact 4.8
| GUnreduced ![Term Ref]
-- ^ Cost of using a native function
| GPostRead !ReadValue
Expand Down Expand Up @@ -204,6 +207,7 @@ instance Pretty GasArgs where
GSelect {} -> "GSelect"
GSortFieldLookup i -> "GSortFieldLookup:" <> pretty i
GConcatenation i j -> "GConcatenation:" <> pretty i <> colon <> pretty j
GTextConcatenation nChars nStrings -> "GTextConcatenation:" <> pretty nChars <> colon <> pretty nStrings
GUnreduced {} -> "GUnreduced"
GPostRead rv -> "GPostRead:" <> pretty rv
GPreWrite wv szVer -> "GWrite:" <> pretty wv <> colon <> pretty szVer
Expand Down
2 changes: 1 addition & 1 deletion tests/GasModelSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ runTest t = runGasUnitTests t run run
let
flags = mkExecutionConfig
[ FlagDisableInlineMemCheck, FlagDisablePactEvents
, FlagDisablePact43, FlagDisablePact44, FlagDisablePact45]
, FlagDisablePact43, FlagDisablePact44, FlagDisablePact45, FlagDisablePact48]
r' = set eeExecutionConfig flags r
pure (r', s)

Expand Down
31 changes: 31 additions & 0 deletions tests/pact/gas.repl
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,34 @@ d.G3
(str-to-list "0123456789")
(expect "gas of str-to-list post-fork" 11 (env-gas))
(commit-tx)


; Test that `concat` is gassed by the number of characters
; at pact-4.8.
(begin-tx)
(env-gaslimit 1000000)
(module m G
(defcap G () true)
(defun ten_x_string (n)
(fold (lambda (acc unused) (+ acc "aaaaaaaaaa")) "" (enumerate 1 n)))
(defconst strings_1000_1000:[string] (make-list 1000 (ten_x_string 100)))
(defconst strings_1000_2000:[string] (make-list 2000 (ten_x_string 100)))
(defconst strings_2000_1000:[string] (make-list 1000 (ten_x_string 200)))
)

; ============================================================ ;
;; TEST: cost of running concat on large strings.
;;
(env-gas 0)
(concat strings_1000_1000)
(expect "calling (concat strings_1000_1000)" 111 (env-gas))

(env-gas 0)
(concat strings_2000_1000)
(expect "calling (concat strings_2000_1000)" 131 (env-gas))

(env-gas 0)
(concat strings_1000_2000)
(expect "calling (concat strings_1000_2000)" 151 (env-gas))

(commit-tx)