Skip to content

HAMT Performance Improvements #54

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

Draft
wants to merge 32 commits into
base: dev-integrity
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
5edc819
Fix 'make clear' is broken
ssoelvsten Aug 15, 2025
f2d53a5
Rename 'make clear/*' into 'make clean/*' to fit conventions
ssoelvsten Aug 15, 2025
cce30de
Move HAMT tests to 'lib' or '_unautomated'
ssoelvsten Aug 15, 2025
d81a8cf
Ignore temporary files from Emacs
ssoelvsten Aug 15, 2025
4467017
Cleanup whitespace and redundant 'optimized' comments
ssoelvsten Aug 15, 2025
92809f4
Remove duplicated and dead code
ssoelvsten Aug 15, 2025
4691751
Reorder functions in HAMT to better align with something that makes s…
ssoelvsten Aug 15, 2025
518d1f8
Fix whitespace in Hash tests
ssoelvsten Aug 15, 2025
663e93e
Move the 'make libs' target into the 'lib' subfolder
ssoelvsten Aug 15, 2025
cb8b32d
Add tests for String standard library
ssoelvsten Aug 15, 2025
38270e4
Remove dead code 'lists.picox'
ssoelvsten Aug 15, 2025
bb9fc5c
Move hash functions to its own part of the Standard Library
ssoelvsten Aug 18, 2025
cbef5d9
Some code cleanup on hash functions
ssoelvsten Aug 18, 2025
dc84860
Fix incorrect choice of 'alpha' for Knuth's hash
ssoelvsten Aug 18, 2025
1305793
Replace use of 'mod' for power-of-two with bitwise and
ssoelvsten Aug 18, 2025
a4796e6
Make 'hashInt' a branchless computation
ssoelvsten Aug 18, 2025
1632598
Expose domain size parameter for Knuth's multiplicative hash
ssoelvsten Aug 18, 2025
9f72c5d
Add crude support for float hashing
ssoelvsten Aug 18, 2025
3f08747
Move magic constant '31' into a descriptive variable
ssoelvsten Aug 19, 2025
24a39f2
Make 'charCode' out-of-bounds result in 'null' rather than '?'
ssoelvsten Aug 19, 2025
e094df0
Increase radix for string hashing
ssoelvsten Aug 19, 2025
d899bca
Increase string hashing to output 31 bits
ssoelvsten Aug 19, 2025
c09393f
Some more code improvements for code readability
ssoelvsten Aug 19, 2025
95915d5
Major cleanup and comments for HAMT implementation
ssoelvsten Aug 19, 2025
a2f3f2e
Add note on what needs to be cleaned up for the Standard Library
ssoelvsten Aug 22, 2025
d9deafa
Remove dead code
ssoelvsten Aug 22, 2025
90115c7
Make List library export in a struct
ssoelvsten Aug 22, 2025
40f40e5
Remove lib compilation artifacts
ssoelvsten Aug 22, 2025
e9be79e
Fix Makefile for lib compilation
ssoelvsten Aug 22, 2025
c6745b6
WIP: Hash
ssoelvsten Aug 22, 2025
7043ffb
WIP: Unit Testing library
ssoelvsten Aug 22, 2025
c7a2886
WIP: StencilVector
ssoelvsten Aug 21, 2025
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
6 changes: 3 additions & 3 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ jobs:
exit 1
fi
echo "Runtime built successfully, troupe.mjs found"
- name: compile libs
run: make libs
- name: compile lib
run: make lib
- name: compile service
run: make service
- name: run basic test
Expand Down Expand Up @@ -137,4 +137,4 @@ jobs:
path: |
/tmp/troupe-multinode-*/
/tmp/tmp.*/
retention-days: 7
retention-days: 7
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ yarn-error.log
bin/troupe
bin/understudy
trp-rt/out/
*.#*
38 changes: 14 additions & 24 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: rt compiler p2p-tools
.PHONY: rt compiler lib p2p-tools

# TODO: Rename to 'build/*' ?
all: npm rt compiler p2p-tools libs service
all: npm rt compiler p2p-tools lib service

npm:
npm install
Expand All @@ -17,33 +17,23 @@ compiler:
p2p-tools:
cd p2p-tools; tsc

libs:
$(COMPILER) ./lib/nsuref.trp -l
$(COMPILER) ./lib/string.trp -l
$(COMPILER) ./lib/printService.trp -l
$(COMPILER) ./lib/lists.trp -l
$(COMPILER) ./lib/NetHealth.trp -l
$(COMPILER) ./lib/declassifyutil.trp -l
$(COMPILER) ./lib/stdio.trp -l
$(COMPILER) ./lib/timeout.trp -l
$(COMPILER) ./lib/raft.trp -l
$(COMPILER) ./lib/raft_debug.trp -l
$(COMPILER) ./lib/bst.trp -l
$(COMPILER) ./lib/localregistry.trp -l
$(COMPILER) ./lib/hamt.trp -l
lib:
cd lib; $(MAKE) build

service:
mkdir -p ./trp-rt/out
$(COMPILER) ./trp-rt/service.trp -l

# TODO: Rename to 'clean/*' ?
clear: clear/stack clear/rt
clear/compiler:
cd compiler; $(MAKE) clear
clear/rt:
cd rt; $(MAKE) clear
clear/p2p-tools:
cd p2p-tools; $(MAKE) clear
clean: clean/compiler clean/rt clean/lib
clean/compiler:
cd compiler; $(MAKE) clean
clean/rt:
cd rt; $(MAKE) clean
clean/p2p-tools:
cd p2p-tools; $(MAKE) clean
clean/lib:
cd lib; $(MAKE) clean

ci-test-golden-no-color:
mkdir -p out
Expand All @@ -66,7 +56,7 @@ test/ci-relay: p2p-tools
@echo "Running CI relay test..."
./tests/ci-relay-test.sh

dist: stack npm rt p2p-tools libs
dist: stack npm rt p2p-tools lib
rm -rf ./build/
mkdir -p ./build/Troupe/rt/built
mkdir -p ./build/Troupe/p2p-tools/built
Expand Down
2 changes: 1 addition & 1 deletion compiler/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ all:
mkdir -p ./../bin
stack -v install $(STACK_OPTS) --local-bin-path ./../bin/

clear:
clean:
rm *.cabal
stack clean --full
rm -rf ../bin
Expand Down
14 changes: 13 additions & 1 deletion compiler/test/Golden.hs
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,14 @@ goldenTests tc = do
warningTestsForRuntime <- findByExtension extensions "tests/rt/warn"
timeoutTestsForRuntime <- findByExtension extensions "tests/rt/timeout/blocking"
divergingTestsForRuntime <- findByExtension extensions "tests/rt/timeout/diverging"
testsForLib <- findByExtension extensions "tests/lib"

return $ (testGroup ("Troupe golden tests (" ++ ppTestConfig tc ++ ")") $ map ($ tc)
[ compilerTests negativeTestsForCompiler
, runtimeTests $ concat [positiveTestsForRuntime, negativeTestsForRuntime, warningTestsForRuntime]
, timeoutTests timeoutTestsForRuntime
, divergingTests divergingTestsForRuntime ] )
, divergingTests divergingTestsForRuntime
, libTests testsForLib] )


compilerTests testFiles tc =
Expand Down Expand Up @@ -196,3 +198,13 @@ divergingTests testFiles tc =
| troupeFile <- testFiles
]

libTests testFiles tc =
testGroup "Library tests"
[ goldenVsStringDiff
troupeFile
diff
(goldenFileName troupeFile tc)
(runPositive troupeFile tc)
| troupeFile <- testFiles
]

4 changes: 2 additions & 2 deletions examples/fromuserguide/basic_lib.trp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import lists
printWithLabels (map ( fn i => i + 1) [1,2,3])
import List
printWithLabels (List.map ( fn i => i + 1) [1,2,3])


6 changes: 3 additions & 3 deletions examples/fromuserguide/basic_spawn.trp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import lists
import List
let fun printwait x = let val _ = printWithLabels x in sleep 10 end
fun foo () = map printwait [1,2,3]
fun bar () = map printwait ["A", "B", "C"]
fun foo () = List.map printwait [1,2,3]
fun bar () = List.map printwait ["A", "B", "C"]
in (spawn foo, spawn bar)
end
8 changes: 4 additions & 4 deletions examples/fromuserguide/ifc_labels.trp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import lists
map (fn (x,lev) => x raisedTo lev) [ (1, `{alice}`)
, (2, `{bob}`)
, (3, `{charlie}`) ]
import List
List.map (fn (x,lev) => x raisedTo lev) [ (1, `{alice}`)
, (2, `{bob}`)
, (3, `{charlie}`) ]

5 changes: 2 additions & 3 deletions examples/network/pingpong/p2ppingpong.trp
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import lists
let fun pingpong () =
let
let
val {counter, pid=sender} = receive [hn x => x]
val _ = send (sender, {counter=counter + 1, pid=self()})
val _ = print counter
Expand All @@ -11,7 +10,7 @@ in let val processA = spawn ("@pingpong-listener", pingpong)
val _ = print processA
val processB = spawn ("@pingpong-dialer", pingpong)
val _ = print processB
val _ = send (processA,{counter = 1, pid =processB})
val _ = send (processA,{counter = 1, pid=processB})
in ()
end
end
66 changes: 66 additions & 0 deletions lib/Hash.trp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
let (** Maximum value available in a 32 bit unsigned integer *)
val maxUInt32 = (1 << 32) - 1

(** A mersenne prime which also happens to be the largest possible value for
* a signed 32 bit integer using two's complement. All hash functions
* should map into the range 0..maxInt32. *)
val maxInt32 = (1 << 31) - 1

(** Hash function for strings.
*
* Uses polynomial rolling hash, i.e. a conversion to a number using a certain
* radix; for performance, strings of length 2 or shorter are evaluated
* non-recursively.
*)
fun hashString s =
(* String hash with fast paths for small strings *)
let val len = strlen s
val radix = 127
fun charCode idx = charCodeAtWithDefault (s, idx, 0)
in case len of 0 => 0
| 1 => charCode 0
| 2 => (radix * charCode 0 + charCode 1) andb maxInt32
| _ => let fun go idx acc =
if len <= idx then acc
else go (idx + 1) ((acc * radix + charCode idx) andb maxInt32)
in go 0 0
end
end

(** The Multiply-Shift as proposed by Dietzfelbinger et al. (also known as
* Knuth's Multiplicative Hash) of a value `i` into a domain of `0..2^l`
* for `l = 0..31`. *)
fun hashMultiplyShift i l =
if l < 0 then 0
else if 31 < l then hashMultiplyShift i 31
else let val alpha = 2654435769
in (i * alpha andb maxInt32) >> (32 - (floor l))
end

(** Hash function for integers. *)
fun hashInt i = hashMultiplyShift i 31

(** Hash function for numbers, including fractions.
*
* TODO: Currently float hashing is merely done by string hashing. Much
* better would be just to bitcast it to an integer. *)
fun hashNumber i = if floor i = i then hashInt i else hashString (toString i)

(** Hash function for any type of value. *)
fun hash x = case getType x of "number" => hashNumber x
| "string" => hashString x
| _ => hashString (toString x)

(*--- Module ---*)
val Hash = {
hashString = hashString,
hashMultiplyShift = hashMultiplyShift,
hashInt = hashInt,
hashNumber = hashNumber,
hash = hash
}

in [ ("Hash", Hash)
, ("hash", hash)
]
end
80 changes: 46 additions & 34 deletions lib/lists.trp → lib/List.trp
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
let fun map f list =
let (* TODO: Sort and document these functions. *)

(* TODO: isList out of the TCB *)

fun map f list =
case list of
[] => []
| (x::xs) => (f x) :: (map f xs)

fun mapi f list =
let fun mapj j ls =
let fun mapj j ls =
case ls of [] => []
| (x::xs) => (f (j,x)) :: (mapj (j+1) xs)
in mapj 0 list
end

(* TODO: foldr *)

fun foldl f y [] = y
| foldl f y (x::xs) = foldl f (f (x,y)) xs

fun null [] = true
| null _ = false

fun length [] = 0
| length (x::xs) = 1 + length xs

Expand All @@ -29,32 +38,30 @@ let fun map f list =
in reverse (rng n)
end

fun append l1 l2 =
case l1 of
[] => l2
| x::l => x::(append l l2)
fun append [] ys = ys
| append x::xs ys = x::(append xs ys)

fun nth (x::l) 1 = x
| nth (x::l) n = nth l (n - 1)

(* TODO: Rename this file to 'List' and move this to 'ListPair' together with
* a definition of 'zip' and 'unzip'. *)
fun lookup list key default =
case list of [] => default
| ((x, v):: xs) =>
if x = key then v
else lookup xs key default

fun elem x [] = false
fun elem x [] = false
| elem x (y::ys) = if x = y then true else elem x ys

fun partition f ls =
let fun partition_aux (a,b) [] = (reverse a, reverse b )
fun partition f ls =
let fun partition_aux (a,b) [] = (reverse a, reverse b )
| partition_aux (a,b) (x::xs) =
if f x then partition_aux (x::a, b) xs
if f x then partition_aux (x::a, b) xs
else partition_aux (a, x::b) xs


in partition_aux ([],[]) ls
end
in partition_aux ([],[]) ls
end

fun filter f l = case l of
[] => []
Expand All @@ -71,28 +78,33 @@ let fun map f list =
| (m, n, h :: t) => slice (m - 1) (n - 1) t
| (_, _, _) => []

fun insert_at_index l appended_l index =
fun insertAt l appended_l index =
let val beg_slice = slice 0 index l
val end_slice = slice index (length l) l
in append beg_slice (append appended_l end_slice)
end


in
[ ("map", map)
, ("mapi", mapi)
, ("foldl", foldl)
, ("range", range)
, ("reverse", reverse)
, ("lookup", lookup)
, ("elem", elem)
, ("length", length)
, ("append", append)
, ("partition", partition)
, ("nth", nth)
, ("filter", filter)
, ("first", first)
, ("slice", slice)
, ("insert_at_index", insert_at_index)
]

(*--- Module ---*)
val List = {
map = map,
mapi = mapi,
foldl = foldl,
range = range,
reverse = reverse,
lookup = lookup,
elem = elem,
null = null,
length = length,
append = append,
partition = partition,
nth = nth,
filter = filter,
first = slice,
slice = slice,
insertAt = insertAt
}

in [ ("List", List),
("length", length)
]
end
22 changes: 22 additions & 0 deletions lib/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
COMPILER=../bin/troupec

build:
mkdir out
$(COMPILER) ./nsuref.trp -l
$(COMPILER) ./string.trp -l
$(COMPILER) ./printService.trp -l
$(COMPILER) ./List.trp -l
$(COMPILER) ./timeout.trp -l
$(COMPILER) ./NetHealth.trp -l
$(COMPILER) ./declassifyutil.trp -l
$(COMPILER) ./stdio.trp -l
$(COMPILER) ./raft.trp -l
$(COMPILER) ./raft_debug.trp -l
$(COMPILER) ./bst.trp -l
$(COMPILER) ./localregistry.trp -l
$(COMPILER) ./Hash.trp -l
$(COMPILER) ./Unit.trp -l
$(COMPILER) ./StencilVector.trp -l

clean:
rm -rf out
Loading
Loading