diff --git a/config_ipa.go b/config_ipa.go index d4483836..fe22c4bb 100644 --- a/config_ipa.go +++ b/config_ipa.go @@ -38,8 +38,8 @@ type IPAConfig struct { type Config = IPAConfig func (ipac *IPAConfig) CommitToPoly(poly []Fr, _ int) *Point { - c := ipac.conf.Commit(poly) - return &c + ret := ipac.conf.Commit(poly) + return &ret } var cfg *Config @@ -69,10 +69,8 @@ func GetConfig() *Config { return cfg } -var ( - FrZero Fr - FrOne Fr -) +var FrZero Fr +var FrOne Fr func init() { FrZero.SetZero() diff --git a/tree.go b/tree.go index c76fed3b..8f92c62d 100644 --- a/tree.go +++ b/tree.go @@ -469,7 +469,10 @@ func (n *InternalNode) Delete(key []byte, resolver NodeResolverFn) error { func (n *InternalNode) Flush(flush NodeFlushFn) { n.Commit() - if n.depth <= 0 { + // If we're at the root internal node, we fire goroutines exploiting + // available cores. Those goroutines will recursively take care of downstream + // layers, so we avoid creating too many goroutines. + if n.depth == 0 { batches := runtime.NumCPU() batchSize := len(n.children) / batches var wg sync.WaitGroup @@ -585,8 +588,12 @@ func (n *InternalNode) Commitment() *Point { } func (n *InternalNode) Commit() *Point { + // If we're at the first or second layer, we do the Commit() in parallel + // leveraging available cores. + // If we're in further layers, we do single-goroutine Commit() since the top + // two layers already created enough goroutines doing downstream work. if n.depth <= 1 { - return n.commitRoot() + return n.commitParallel() } if len(n.cow) != 0 { polyp := frPool.Get().(*[]Fr) @@ -627,7 +634,7 @@ func (n *InternalNode) Commit() *Point { return n.commitment } -func (n *InternalNode) commitRoot() *Point { +func (n *InternalNode) commitParallel() *Point { if len(n.cow) != 0 { polyp := frPool.Get().(*[]Fr) poly := *polyp @@ -639,50 +646,74 @@ func (n *InternalNode) commitRoot() *Point { }() emptyChildren := 256 + // The idea below is to distribute calling Commit() in all COW-ed children in multiple goroutines. + // For example, if we have 2-cores, we calculate the first half of COW-ed new Commit() in a goroutine, and + // the other half in another goroutine. + + // In `points` we'll have: + // - point[2*i]: the previous *Point value saved by COW. + // - point[2*i+1]: we'll calculate the new Commit() of that leaf value. + // + // First, we create the arrays to store this. var i int - b := make([]byte, len(n.cow)) + pointsIndexes := make([]byte, len(n.cow)) points := make([]*Point, 2*len(n.cow)) for idx := range n.cow { emptyChildren-- - b[i] = idx + pointsIndexes[i] = idx i++ } var wg sync.WaitGroup - f := func(start, end int) { + // `calculateChildsCommsInRange` does the mentioned calculation in `points`. + // It receives the range in the array where it should do the work. As mentioned earlier, each goroutine + // is assigned a range to do work. The complete range work is distributed in multiple goroutines. + calculateChildsCommsInRange := func(start, end int) { defer wg.Done() for i := start; i < end; i++ { - points[2*i] = n.cow[b[i]] - points[2*i+1] = n.children[b[i]].Commit() + points[2*i] = n.cow[pointsIndexes[i]] + points[2*i+1] = n.children[pointsIndexes[i]].Commit() } } + // Here we do the work distribution. We split the total range of work to do in `numBatches` batches and call + // the above function which does the work. numBatches := runtime.NumCPU() wg.Add(numBatches) for i := 0; i < numBatches; i++ { + batchStart := i * (len(pointsIndexes) / numBatches) if i < numBatches-1 { - go f(i*(len(b)/numBatches), (i+1)*(len(b)/numBatches)) + go calculateChildsCommsInRange(batchStart, (i+1)*(len(pointsIndexes)/numBatches)) } else { - go f(i*(len(b)/numBatches), len(b)) + go calculateChildsCommsInRange(batchStart, len(pointsIndexes)) } } + // After calculating the new *Point (Commit() of touched children), we'll have to do the Point->Fr transformation. frs := make([]*Fr, len(points)) for i := range frs { if i%2 == 0 { + // For even slots (old COW commitment), we create a new empty Fr to store the result. frs[i] = &Fr{} } else { - frs[i] = &poly[b[i/2]] + // For odd slots (new commitment), we can use `poly` as a temporal storage to avoid allocations. + frs[i] = &poly[pointsIndexes[i/2]] } } wg.Wait() + // Now that in `frs` we have where we want to store *all* the Point->Fr transformations, we do that in a single batch. toFrMultiple(frs, points) + + // For each for i := 0; i < len(points)/2; i++ { - poly[b[i]].Sub(frs[2*i+1], frs[2*i]) + // Now we do [newCommitment] - [oldCommitment], so we know the Fr difference between old and new commitments. + poly[pointsIndexes[i]].Sub(frs[2*i+1], frs[2*i]) } n.cow = nil + // Now that in `poly` we have the Fr differences, we `CommitToPoly` and add to the current internal node + // commitment, finishing the diff-updating. n.commitment.Add(n.commitment, cfg.CommitToPoly(poly, emptyChildren)) return n.commitment } @@ -1114,24 +1145,15 @@ func (leaf *LeafNode) Commit() *Point { // Initialize the commitment with the extension tree // marker and the stem. count := 0 - polyp, c1polyp := frPool.Get().(*[]Fr), frPool.Get().(*[]Fr) - // TODO(jsign): remove "poly" and reuse again c1poly - poly, c1poly := *polyp, *c1polyp + c1polyp := frPool.Get().(*[]Fr) + c1poly := *c1polyp defer func() { for i := 0; i < 256; i++ { - poly[i] = Fr{} c1poly[i] = Fr{} } - frPool.Put(polyp) frPool.Put(c1polyp) }() - poly[0].SetUint64(1) - StemFromBytes(&poly[1], leaf.stem) - // TODO(jsign) - if len(leaf.values) != 256 { - panic("leaf doesn't have 256 values") - } count = fillSuffixTreePoly(c1poly[:], leaf.values[:128]) leaf.c1 = cfg.CommitToPoly(c1poly[:], 256-count) @@ -1141,8 +1163,14 @@ func (leaf *LeafNode) Commit() *Point { count = fillSuffixTreePoly(c1poly[:], leaf.values[128:]) leaf.c2 = cfg.CommitToPoly(c1poly[:], 256-count) - toFrMultiple([]*Fr{&poly[2], &poly[3]}, []*Point{leaf.c1, leaf.c2}) - leaf.commitment = cfg.CommitToPoly(poly[:], 252) + for i := 0; i < 256; i++ { + c1poly[i] = Fr{} + } + c1poly[0].SetUint64(1) + StemFromBytes(&c1poly[1], leaf.stem) + + toFrMultiple([]*Fr{&c1poly[2], &c1poly[3]}, []*Point{leaf.c1, leaf.c2}) + leaf.commitment = cfg.CommitToPoly(c1poly[:], 252) } else if len(leaf.cow) != 0 { // If we've already have a calculated commitment, and there're touched leaf values, we do a diff update.