-
Notifications
You must be signed in to change notification settings - Fork 375
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
perf: make tests run faster #1417
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #1417 +/- ##
==========================================
- Coverage 55.85% 55.23% -0.62%
==========================================
Files 431 432 +1
Lines 65729 67634 +1905
==========================================
+ Hits 36713 37360 +647
- Misses 26140 27375 +1235
- Partials 2876 2899 +23
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
3883e1b
to
14bd0ea
Compare
@petar-dambovaliev ready for review? I'll try to take a look at it this week. (It's close to the top of my review-list, so should be able to take a look at it, but no guarantees). Thanks for the hard work. |
Should be ready for viewing as far as I know |
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.
Ran this locally, seems to shave ~1min on my machine for make test
in the gnovm
subfolder 🎉
Thank you for removing globals. I see this PR as a good start 👍
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.
doing a partial review, GitHub is failing to load the diff.
func (d *Debugging) DeepCopy() *Debugging { | ||
if d == nil { | ||
return nil | ||
} | ||
|
||
var enabled bool = true | ||
deers := make([]string, len(d.derrors)) | ||
|
||
func (d debugging) Println(args ...interface{}) { | ||
if d { | ||
if enabled { | ||
fmt.Println(append([]interface{}{"DEBUG:"}, args...)...) | ||
} | ||
copy(deers, d.derrors) | ||
|
||
return &Debugging{ | ||
enabled: d.enabled, | ||
derrors: deers, | ||
} | ||
} |
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.
why is this needed?
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.
Agreed with @ajnavarro. Because we're only appending to d.derrors
(never changing items, only appending or setting it directly to nil) it's actually safe to return derrors
directly.
if d == nil { | ||
return 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.
why check d is nill? this might swallow implementation errors.
func IsDebug() bool { | ||
return bool(debug) | ||
func (d *Debugging) IsDebug() bool { | ||
return d != 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.
return d != nil | |
return d.enabled |
func (d *Debugging) DisableDebug() { | ||
if d != nil { | ||
d.enabled = false | ||
} | ||
} | ||
|
||
func EnableDebug() { | ||
enabled = true | ||
func (d *Debugging) EnableDebug() { | ||
if d != nil { | ||
d.enabled = true | ||
} | ||
} |
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.
Instead of disable/enable debug, why not just SetEnabled(bool)
?
// using a const is probably faster. | ||
// const debug debugging = true // or flip | ||
var debug debugging = false | ||
type Debugging struct { |
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.
Why instead of passing it around, don't we just use the singleton pattern used in go log implementations?
var Log = &debugging{enabled:true}
type debugging struct {
...
}
[...]
In any case, it must be thread-safe.
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.
So, I had a feeling that adding new pointers to all of these structs may actually not be the best thing for performance. The tests may run faster, but overall execution is actually slower:
goos: linux
goarch: amd64
pkg: github.com/gnolang/gno/gnovm/pkg/gnolang
cpu: AMD Ryzen 7 PRO 4750U with Radeon Graphics
│ bench_master.txt │ bench_1406.txt │
│ sec/op │ sec/op vs base │
Benchdata/fib.gno_param:4-16 32.27µ ± 5% 32.47µ ± 7% ~ (p=0.393 n=10)
Benchdata/fib.gno_param:8-16 228.8µ ± 5% 236.0µ ± 1% +3.15% (p=0.000 n=10)
Benchdata/fib.gno_param:16-16 11.02m ± 4% 10.91m ± 5% ~ (p=0.481 n=10)
Benchdata/loop.gno-16 493.3n ± 11% 698.5n ± 6% +41.60% (p=0.000 n=10)
Benchdata/matrix.gno_param:3-16 653.9µ ± 4% 707.7µ ± 6% +8.22% (p=0.004 n=10)
Benchdata/matrix.gno_param:4-16 1.862m ± 3% 2.081m ± 4% +11.74% (p=0.000 n=10)
Benchdata/matrix.gno_param:5-16 6.822m ± 6% 7.643m ± 5% +12.04% (p=0.000 n=10)
Benchdata/matrix.gno_param:6-16 34.66m ± 5% 38.16m ± 5% +10.11% (p=0.000 n=10)
geomean 572.6µ 630.8µ +10.17%
go test -v -bench 'Benchdata' -run 'NONE' -count 10 | tee ~/throw/bench_1406.txt
These are tests done with the benchmarking system I added in #1624 (note it specifically targets benchmarking execution, not preprocessing) and as you can see there is an average 10% more time taken on each operation.
I think this makes clear our need for good benchmarking and proves the point of setting this up in #1624. Please, if you see problems with the methodology, or see a benchmark which disproves these benchmarks, do point them out. I pushed the branch with this branch + the changes in #1624 in 1406-with-bench
I think there are some foundational ideas in this PR which are good, some which need to be iterated before we can have tests which are both parallelized but don't sacrifice execution-time performance.
Thanks!
@@ -39,7 +39,7 @@ fmt: | |||
.PHONY: test | |||
test: _test.cmd _test.pkg _test.gnolang | |||
|
|||
GOTEST_FLAGS ?= -v -p 1 -timeout=30m | |||
GOTEST_FLAGS ?= -timeout=30m |
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.
I get removing -p
(also because reading the docs looks like it wasn't all that necessary) but why remove -v
?
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.
It is giving out a lot of spam. You generally don't care for verbose when running all tests.
You care about verbose when running a single test.
@@ -123,7 +123,7 @@ func (x IndexExpr) String() string { | |||
} | |||
|
|||
func (x SelectorExpr) String() string { | |||
// NOTE: for debugging selector issues: | |||
// NOTE: for selector issues: |
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.
lol?
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.
intellij find and replace
// using a const is probably faster. | ||
// const debug debugging = true // or flip | ||
var debug debugging = false | ||
type Debugging struct { |
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.
Please add type documentation
// const debug debugging = true // or flip | ||
var debug debugging = false | ||
type Debugging struct { | ||
enabled bool |
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.
If we have a Debugging type and it's passed as a pointer, would its mere existance not mean that it is supposed to be enabled?
We could keep things as they are as well. Ie. m.Debugging.Println
, func (d *Debugging) Println
checks that d != nil
before doing so...
func (d *Debugging) DeepCopy() *Debugging { | ||
if d == nil { | ||
return nil | ||
} | ||
|
||
var enabled bool = true | ||
deers := make([]string, len(d.derrors)) | ||
|
||
func (d debugging) Println(args ...interface{}) { | ||
if d { | ||
if enabled { | ||
fmt.Println(append([]interface{}{"DEBUG:"}, args...)...) | ||
} | ||
copy(deers, d.derrors) | ||
|
||
return &Debugging{ | ||
enabled: d.enabled, | ||
derrors: deers, | ||
} | ||
} |
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.
Agreed with @ajnavarro. Because we're only appending to d.derrors
(never changing items, only appending or setting it directly to nil) it's actually safe to return derrors
directly.
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.
So, summary of what I think about the way you modified this:
- I think we should simplify "enabled" to d != nil. If at one point we want to disable debugging, since we're passing it around as function parameters and in machines, we just set it to nil where necessary. Removes the necessity for functions Enable/DisableDebug.
- Furthermore, I think it makes sense to create, together with this, an option to disable debugging at compile-time entirely. This can be a simple constant, maybe in an
internal/
package if we don't want to pollute the gnolang directory even further, with the two following files/variations:
//go:build !nodebug
package internal
const DebuggingEnabled = true
//go:build nodebug
package internal
const DebuggingEnabled = false
At this point we need to check DebuggingEnabled in each function here (can alias it, too). Since it's a constant, the compiler can see it and remove the entire function body, and probably inline a lot of functions using debugging along the way.
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.
So, I don't think we necessarily need what @ajnavarro mentioned (ie. making debugging
a singleton like it was before the PR), but I do think we need to split its two uses, which actually completely avoids what happened in this file: that every file type now gets the debugging variable.
I suggest that for all cases where the debugging variable guards an additional check like the vast majority of the ones here, that we instead opt for the same solution I'm proposing to disable debugging at compile time: build tags. These are all checks which provide "additional safety", so I would have a value like const Safety = true
, enabled by default, that can be disabled with build tag -tags optimize
or -tags nosafety
.
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.
Sorry, meant to indicate request changes.
#1417 (comment) |
solves issue
This PR enables running tests in parallel by making the
Machine
data structure copy-able and thus threadsafe.DeepCopy
methods added to all types that are contained within theMachine
. including types that implement interfacesExpr
,Object
,Store
andType
.Before each test is launched, the
Machine
object is deep copied and passed along so that test can read and write on its own instance.Global debugging functionality, including the accumulation of errors was moved into local scopes.
the
var preprocessing int
counter was moved into its own type with the relevant methods and its passed as an argumentDebugging is a field of every struct that needs to use it (i tried adding it as a method argument first, it didn't end well. The code was becoming extremely noisy)