dcrd/chaincfg/generatesubsidytables.go
Josh Rickmar f860883562 chaincfg: Avoid block 1 subsidy codegen explosion.
chaincfg/v2 modified the Params type to contain the required payouts
created by the miner of the block at height 1.  However, it was
discovered that, due to code generation issues of the Go compiler,
this caused the package's weight (measured by
github.com/jondot/goweight) to explode to 7.7 MB.  This appears to be
due to an enormous amount of function calls, all of which could panic,
when building the large slice literal.

This improves the compiled size of the package by using code
generation techniques to optimize the memory layout and code
transformation of the subsidy values each time a params function is
called.  A single string contains all scripts for subsidy payouts
concatenated together, in hex encoding, and a slice contains the index
in the script data and the required payout amount.  This is iterated
in a loop to create a []TokenPayout each time it is needed.

The new size of the compiled package using Go 1.12.7 on OpenBSD/amd64
is 379 kB, a reduction of 7.32 MB, and about 5% of the original size.
2019-07-28 14:19:13 -05:00

98 lines
2.2 KiB
Go

// Copyright (c) 2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
//+build ignore
package main
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"log"
"os"
"strings"
"github.com/decred/dcrd/chaincfg/v2"
)
type payout struct {
offset int
amount int64
}
type payouts []payout
func (p payouts) GoString() string {
var s strings.Builder
s.WriteString("[]blockOnePayout{\n")
for i := range p {
s.WriteString("\t")
s.WriteString(fmt.Sprintf("{offset: %#v, amount: %#v}", p[i].offset, p[i].amount))
s.WriteString(",\n")
}
s.WriteString("}")
return s.String()
}
type printer func(format string, args ...interface{})
func main() {
buf := new(bytes.Buffer)
p := func(format string, args ...interface{}) {
fmt.Fprintf(buf, format, args...)
}
p("// autogenerated by generatesubsidytable.go; do not edit\n\n")
p("package chaincfg\n")
for _, g := range []struct {
name string
tokenPayouts []chaincfg.TokenPayout
}{
{"MainNetParams", chaincfg.MainNetSubsidyDefinition},
{"TestNet3Params", chaincfg.TestNet3SubsidyDefinition},
{"SimNetParams", chaincfg.SimNetSubsidyDefinition},
{"RegNetParams", chaincfg.RegNetSubsidyDefinition},
} {
p("\n")
defs(p, g.name, g.tokenPayouts)
}
out, err := os.Create("subsidytables.go")
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(out, buf)
if err != nil {
log.Fatal(err)
}
}
func defs(p printer, paramsName string, tokenPayouts []chaincfg.TokenPayout) {
var scripts strings.Builder
var payouts payouts
var offset int
for _, t := range tokenPayouts {
if t.ScriptVersion != 0 {
log.Fatalf("this tool only generates code where all scripts are version 0")
}
_, err := scripts.WriteString(hex.EncodeToString(t.Script))
if err != nil {
log.Panic(err)
}
offset += len(t.Script)
payouts = append(payouts, payout{
offset: offset,
amount: t.Amount,
})
}
p("const blockOnePayoutScripts_%s = %#v\n\n", paramsName, scripts.String())
p("var blockOnePayouts_%s = %#v\n\n", paramsName, payouts)
p("func tokenPayouts_%s() []TokenPayout {\n"+
"\treturn tokenPayouts(blockOnePayoutScripts_%[1]s, blockOnePayouts_%[1]s)\n"+
"}\n", paramsName)
}