@@ -469,7 +469,10 @@ func (n *InternalNode) Delete(key []byte, resolver NodeResolverFn) error {
469
469
func (n * InternalNode ) Flush (flush NodeFlushFn ) {
470
470
n .Commit ()
471
471
472
- if n .depth <= 0 {
472
+ // If we're at the root internal node, we fire goroutines exploiting
473
+ // available cores. Those goroutines will recursively take care of downstream
474
+ // layers, so we avoid creating too many goroutines.
475
+ if n .depth == 0 {
473
476
batches := runtime .NumCPU ()
474
477
batchSize := len (n .children ) / batches
475
478
var wg sync.WaitGroup
@@ -585,8 +588,12 @@ func (n *InternalNode) Commitment() *Point {
585
588
}
586
589
587
590
func (n * InternalNode ) Commit () * Point {
591
+ // If we're at the first or second layer, we do the Commit() in parallel
592
+ // leveraging available cores.
593
+ // If we're in further layers, we do single-goroutine Commit() since the top
594
+ // two layers already created enough goroutines doing downstream work.
588
595
if n .depth <= 1 {
589
- return n .commitRoot ()
596
+ return n .commitParallel ()
590
597
}
591
598
if len (n .cow ) != 0 {
592
599
polyp := frPool .Get ().(* []Fr )
@@ -627,7 +634,7 @@ func (n *InternalNode) Commit() *Point {
627
634
return n .commitment
628
635
}
629
636
630
- func (n * InternalNode ) commitRoot () * Point {
637
+ func (n * InternalNode ) commitParallel () * Point {
631
638
if len (n .cow ) != 0 {
632
639
polyp := frPool .Get ().(* []Fr )
633
640
poly := * polyp
@@ -639,50 +646,74 @@ func (n *InternalNode) commitRoot() *Point {
639
646
}()
640
647
emptyChildren := 256
641
648
649
+ // The idea below is to distribute calling Commit() in all COW-ed children in multiple goroutines.
650
+ // For example, if we have 2-cores, we calculate the first half of COW-ed new Commit() in a goroutine, and
651
+ // the other half in another goroutine.
652
+
653
+ // In `points` we'll have:
654
+ // - point[2*i]: the previous *Point value saved by COW.
655
+ // - point[2*i+1]: we'll calculate the new Commit() of that leaf value.
656
+ //
657
+ // First, we create the arrays to store this.
642
658
var i int
643
- b := make ([]byte , len (n .cow ))
659
+ pointsIndexes := make ([]byte , len (n .cow ))
644
660
points := make ([]* Point , 2 * len (n .cow ))
645
661
for idx := range n .cow {
646
662
emptyChildren --
647
- b [i ] = idx
663
+ pointsIndexes [i ] = idx
648
664
i ++
649
665
}
650
666
651
667
var wg sync.WaitGroup
652
- f := func (start , end int ) {
668
+ // `calculateChildsCommsInRange` does the mentioned calculation in `points`.
669
+ // It receives the range in the array where it should do the work. As mentioned earlier, each goroutine
670
+ // is assigned a range to do work. The complete range work is distributed in multiple goroutines.
671
+ calculateChildsCommsInRange := func (start , end int ) {
653
672
defer wg .Done ()
654
673
for i := start ; i < end ; i ++ {
655
- points [2 * i ] = n.cow [b [i ]]
656
- points [2 * i + 1 ] = n.children [b [i ]].Commit ()
674
+ points [2 * i ] = n.cow [pointsIndexes [i ]]
675
+ points [2 * i + 1 ] = n.children [pointsIndexes [i ]].Commit ()
657
676
}
658
677
}
678
+ // Here we do the work distribution. We split the total range of work to do in `numBatches` batches and call
679
+ // the above function which does the work.
659
680
numBatches := runtime .NumCPU ()
660
681
wg .Add (numBatches )
661
682
for i := 0 ; i < numBatches ; i ++ {
683
+ batchStart := i * (len (pointsIndexes ) / numBatches )
662
684
if i < numBatches - 1 {
663
- go f ( i * ( len ( b ) / numBatches ) , (i + 1 )* (len (b )/ numBatches ))
685
+ go calculateChildsCommsInRange ( batchStart , (i + 1 )* (len (pointsIndexes )/ numBatches ))
664
686
} else {
665
- go f ( i * ( len ( b ) / numBatches ) , len (b ))
687
+ go calculateChildsCommsInRange ( batchStart , len (pointsIndexes ))
666
688
}
667
689
}
668
690
691
+ // After calculating the new *Point (Commit() of touched children), we'll have to do the Point->Fr transformation.
669
692
frs := make ([]* Fr , len (points ))
670
693
for i := range frs {
671
694
if i % 2 == 0 {
695
+ // For even slots (old COW commitment), we create a new empty Fr to store the result.
672
696
frs [i ] = & Fr {}
673
697
} else {
674
- frs [i ] = & poly [b [i / 2 ]]
698
+ // For odd slots (new commitment), we can use `poly` as a temporal storage to avoid allocations.
699
+ frs [i ] = & poly [pointsIndexes [i / 2 ]]
675
700
}
676
701
}
677
702
wg .Wait ()
678
703
704
+ // Now that in `frs` we have where we want to store *all* the Point->Fr transformations, we do that in a single batch.
679
705
toFrMultiple (frs , points )
706
+
707
+ // For each
680
708
for i := 0 ; i < len (points )/ 2 ; i ++ {
681
- poly [b [i ]].Sub (frs [2 * i + 1 ], frs [2 * i ])
709
+ // Now we do [newCommitment] - [oldCommitment], so we know the Fr difference between old and new commitments.
710
+ poly [pointsIndexes [i ]].Sub (frs [2 * i + 1 ], frs [2 * i ])
682
711
}
683
712
684
713
n .cow = nil
685
714
715
+ // Now that in `poly` we have the Fr differences, we `CommitToPoly` and add to the current internal node
716
+ // commitment, finishing the diff-updating.
686
717
n .commitment .Add (n .commitment , cfg .CommitToPoly (poly , emptyChildren ))
687
718
return n .commitment
688
719
}
@@ -1114,24 +1145,15 @@ func (leaf *LeafNode) Commit() *Point {
1114
1145
// Initialize the commitment with the extension tree
1115
1146
// marker and the stem.
1116
1147
count := 0
1117
- polyp , c1polyp := frPool .Get ().(* []Fr ), frPool .Get ().(* []Fr )
1118
- // TODO(jsign): remove "poly" and reuse again c1poly
1119
- poly , c1poly := * polyp , * c1polyp
1148
+ c1polyp := frPool .Get ().(* []Fr )
1149
+ c1poly := * c1polyp
1120
1150
defer func () {
1121
1151
for i := 0 ; i < 256 ; i ++ {
1122
- poly [i ] = Fr {}
1123
1152
c1poly [i ] = Fr {}
1124
1153
}
1125
- frPool .Put (polyp )
1126
1154
frPool .Put (c1polyp )
1127
1155
}()
1128
- poly [0 ].SetUint64 (1 )
1129
- StemFromBytes (& poly [1 ], leaf .stem )
1130
1156
1131
- // TODO(jsign)
1132
- if len (leaf .values ) != 256 {
1133
- panic ("leaf doesn't have 256 values" )
1134
- }
1135
1157
count = fillSuffixTreePoly (c1poly [:], leaf .values [:128 ])
1136
1158
leaf .c1 = cfg .CommitToPoly (c1poly [:], 256 - count )
1137
1159
@@ -1141,8 +1163,14 @@ func (leaf *LeafNode) Commit() *Point {
1141
1163
count = fillSuffixTreePoly (c1poly [:], leaf .values [128 :])
1142
1164
leaf .c2 = cfg .CommitToPoly (c1poly [:], 256 - count )
1143
1165
1144
- toFrMultiple ([]* Fr {& poly [2 ], & poly [3 ]}, []* Point {leaf .c1 , leaf .c2 })
1145
- leaf .commitment = cfg .CommitToPoly (poly [:], 252 )
1166
+ for i := 0 ; i < 256 ; i ++ {
1167
+ c1poly [i ] = Fr {}
1168
+ }
1169
+ c1poly [0 ].SetUint64 (1 )
1170
+ StemFromBytes (& c1poly [1 ], leaf .stem )
1171
+
1172
+ toFrMultiple ([]* Fr {& c1poly [2 ], & c1poly [3 ]}, []* Point {leaf .c1 , leaf .c2 })
1173
+ leaf .commitment = cfg .CommitToPoly (c1poly [:], 252 )
1146
1174
1147
1175
} else if len (leaf .cow ) != 0 {
1148
1176
// If we've already have a calculated commitment, and there're touched leaf values, we do a diff update.
0 commit comments