-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Improve gc for isunspendable method #1615
Conversation
New benchmark results for commit
Since the tests also pass now, I'll make this a non draft. I'd also like to note that the benchmark is not an authoritative measure of performance. We should build methods that help build benchmarks which represent more of a real world load, take these results with a grain of salt! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK. Good find and quite clever. 👍
// return the list of opcodes up until the point of failure so that this can be | ||
// used in functions which do not necessarily have a need for the failed list of | ||
// opcodes, such as IsUnspendable. | ||
// Not returning the full opcode list up until failure also has the benefit of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably want an additional new line here.
txscript/script.go
Outdated
// A script of length zero is an unspendable script but it is parseable. | ||
if len(script) == 0 { | ||
return 0, 0x00, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this already taken care of by the following two lines?
var firstOpcode byte = OP_0
var numParsedInstr uint = 0
The comment still deserves a mention.
txscript/script.go
Outdated
return 0, 0x00, nil | ||
} | ||
|
||
var firstOpcode byte = OP_0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like a misuse of OP_0
. If I understand correctly, using any opcode will work here as long as it is not OP_RETURN
.
txscript/script.go
Outdated
numberOfPops, firstOpcode, err := checkScriptTemplateParseable(pkScript, &opcodeArray) | ||
if err != nil { | ||
return true | ||
} | ||
|
||
return len(pops) > 0 && pops[0].opcode.value == OP_RETURN | ||
return numberOfPops > 0 && firstOpcode == OP_RETURN |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about using (*byte, error)
as the return type of checkScriptTemplateParseable
? The check would simply then be:
firstOpcode, err := checkScriptTemplateParseable(pkScript, &opcodeArray)
if err != nil {
return true
}
return firstOpcode != nil && *firstOpcode == OP_RETURN
This takes into account my other remark that if the script is empty, it doesn't make sense for the first opcode to be OP_0
.
@@ -851,10 +952,14 @@ func getWitnessSigOps(pkScript []byte, witness wire.TxWitness) int { | |||
// guaranteed to fail at execution. This allows inputs to be pruned instantly | |||
// when entering the UTXO set. | |||
func IsUnspendable(pkScript []byte) bool { | |||
pops, err := parseScript(pkScript) | |||
// Not provably unspendable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this necessary? It appears to me that it is already taken care of in checkScriptTemplateParseable
.
txscript/script_test.go
Outdated
}, | ||
{ | ||
// Unspendable | ||
pkScript: []byte{0x6a}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pkScript: []byte{0x6a}, | |
pkScript: []byte{OP_RETURN}, |
txscript/script_test.go
Outdated
}, | ||
{ | ||
// Spendable | ||
pkScript: []byte{0x51}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pkScript: []byte{0x51}, | |
pkScript: []byte{OP_TRUE}, |
@@ -4301,6 +4301,28 @@ func TestIsUnspendable(t *testing.T) { | |||
0xfa, 0x0b, 0x5c, 0x88, 0xac}, | |||
expected: false, | |||
}, | |||
{ | |||
// Spendable | |||
pkScript: []byte{0xa9, 0x14, 0x82, 0x1d, 0xba, 0x94, 0xbc, 0xfb, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whenever possible, it's nice to use human-friendly names for the opcodes. It's not necessary in this example, but I added some suggestions for the other test cases you added.
txscript/script.go
Outdated
op := &opcodes[instr] | ||
pop := parsedOpcode{opcode: op} | ||
|
||
// Parse data out of instruction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The entire switch
block appears to be duplicated in parseScriptTemplate
. Do you think it's worth doing a refactoring?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great but needs to address @onyb's comments then it can go in.
I think this needs to be rebased for the proper checks too. |
9266f14
to
12cc256
Compare
Pull Request Test Coverage Report for Build 426674894
💛 - Coveralls |
12cc256
to
fe077fe
Compare
- create benchmarks to measure allocations - add test for benchmark input - create a low alloc parseScriptTemplate - refactor parsing logic for a single opcode
fe077fe
to
6ee99ed
Compare
@onyb could you take another look at this one to see if your comments were addressed? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me too now.
OK
Through profiling, I've found that the
IsUnspendable
method is a large source of allocations during the sync process.In an effort to reduce the allocations made by this method, and in the sync process overall, I've simply removed the part of
parseScriptTemplate
which allocates and appends to an array (which never ends up being used byIsUnspendable
) and pointedIsUnspendable
to that method, calledcheckScriptTemplateParseable
.To show that this actually did reduce the allocations (and runtime) of the
IsUnspendable
method, I've created a benchmark and run it on both versions of the code, once on thee761244
commit, and once on the0963732
commit.I used the command on the older commit:
I then used this command on the newer commit:
After running these,
benchstat
yielded the following result:The benchtime and count in my benchmarks are unnecessarily high, I get similar results with a lower count and deleted benchtime parameter:
This shows that in at least my benchmarks, the allocations and runtime of this method were significantly reduced.
In the future we should definitely backport changes from dcrd #1656 (I will make an issue for this), but that would be a massive PR to consensus critical code, would likely need to be adapted, and the improvements for this method seem good enough for a single, easily reviewable PR.
The point of this PR is to:
IsUnspendable
.txscript
package, and it would go a very long way towards improving the sync time if we focus on performance using benchmarking and profiling tools.This is a draft right now because a test isn't passing but this will be fixed soon.
Backporting the benchmark changes would be a great future addition in itself, and hopefully this PR highlights this.