mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 17:42:24 -05:00 
			
		
		
		
	[performance] use our own typed value context types for Value() key checking to improve performance (#4316)
Replaces our gtscontext package context.Context handling with our own typed contexts instead of `context.WithValue()`.
I wrote a quick benchmark consisting of (printlns to stop the compiler optimizing instructions away):
```golang
func BenchmarkContexts(b *testing.B) {
	var receiving *gtsmodel.Account
	var requesting *gtsmodel.Account
	var otherIRIs []*url.URL
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			ctx := context.Background()
			ctx = gtscontext.SetBarebones(ctx)
			ctx = gtscontext.SetFastFail(ctx)
			ctx = gtscontext.SetDryRun(ctx)
			ctx = gtscontext.SetReceivingAccount(ctx, receiving)
			ctx = gtscontext.SetRequestingAccount(ctx, requesting)
			ctx = gtscontext.SetOtherIRIs(ctx, otherIRIs)
			if !gtscontext.Barebones(ctx) {
				println("oh no!")
			}
			if !gtscontext.IsFastfail(ctx) {
				println("oh no!")
			}
			if !gtscontext.DryRun(ctx) {
				println("oh no!")
			}
			if gtscontext.ReceivingAccount(ctx) != nil {
				println("oh no!")
			}
			if gtscontext.RequestingAccount(ctx) != nil {
				println("oh no!")
			}
			if len(gtscontext.OtherIRIs(ctx)) > 0 {
				println("oh no!")
			}
		}
	})
}
```
Before results:
```shell
kim @ ~/Projects/main/gts.4
--> go test -v -run=none -bench=.* -benchmem ./internal/gtscontext/ -count=5
goos: linux
goarch: amd64
pkg: code.superseriousbusiness.org/gotosocial/internal/gtscontext
cpu: AMD Ryzen 7 7840U w/ Radeon  780M Graphics
BenchmarkContexts
BenchmarkContexts-16            19050348                61.73 ns/op          288 B/op          6 allocs/op
BenchmarkContexts-16            18245772                61.71 ns/op          288 B/op          6 allocs/op
BenchmarkContexts-16            18853680                61.80 ns/op          288 B/op          6 allocs/op
BenchmarkContexts-16            18561621                62.67 ns/op          288 B/op          6 allocs/op
BenchmarkContexts-16            17819241                62.89 ns/op          288 B/op          6 allocs/op
PASS
ok      code.superseriousbusiness.org/gotosocial/internal/gtscontext    6.112s
```
After results:
```shell
kim @ ~/Projects/main/gts.4
--> go test -v -run=none -bench=.* -benchmem ./internal/gtscontext/ -count=5
goos: linux
goarch: amd64
pkg: code.superseriousbusiness.org/gotosocial/internal/gtscontext
cpu: AMD Ryzen 7 7840U w/ Radeon  780M Graphics
BenchmarkContexts
BenchmarkContexts-16            28038618                41.67 ns/op          144 B/op          6 allocs/op
BenchmarkContexts-16            26537552                42.50 ns/op          144 B/op          6 allocs/op
BenchmarkContexts-16            26720542                42.39 ns/op          144 B/op          6 allocs/op
BenchmarkContexts-16            27408031                43.15 ns/op          144 B/op          6 allocs/op
BenchmarkContexts-16            25597026                44.02 ns/op          144 B/op          6 allocs/op
PASS
ok      code.superseriousbusiness.org/gotosocial/internal/gtscontext    5.997s
```
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4316
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
	
	
This commit is contained in:
		
					parent
					
						
							
								07f61a13de
							
						
					
				
			
			
				commit
				
					
						c8a4ce9a88
					
				
			
		
					 2 changed files with 200 additions and 12 deletions
				
			
		|  | @ -57,7 +57,16 @@ func DryRun(ctx context.Context) bool { | ||||||
| // SetDryRun sets the "dryrun" context flag and returns this wrapped context. | // SetDryRun sets the "dryrun" context flag and returns this wrapped context. | ||||||
| // See DryRun() for further information on the "dryrun" context flag. | // See DryRun() for further information on the "dryrun" context flag. | ||||||
| func SetDryRun(ctx context.Context) context.Context { | func SetDryRun(ctx context.Context) context.Context { | ||||||
| 	return context.WithValue(ctx, dryRunKey, struct{}{}) | 	return dryRunContext{ctx} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type dryRunContext struct{ context.Context } | ||||||
|  | 
 | ||||||
|  | func (ctx dryRunContext) Value(key any) any { | ||||||
|  | 	if key == dryRunKey { | ||||||
|  | 		return struct{}{} | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RequestID returns the request ID associated with context. This value will usually | // RequestID returns the request ID associated with context. This value will usually | ||||||
|  | @ -72,7 +81,19 @@ func RequestID(ctx context.Context) string { | ||||||
| // SetRequestID stores the given request ID value and returns the wrapped | // SetRequestID stores the given request ID value and returns the wrapped | ||||||
| // context. See RequestID() for further information on the request ID value. | // context. See RequestID() for further information on the request ID value. | ||||||
| func SetRequestID(ctx context.Context, id string) context.Context { | func SetRequestID(ctx context.Context, id string) context.Context { | ||||||
| 	return context.WithValue(ctx, requestIDKey, id) | 	return requestIDContext{Context: ctx, requestID: id} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type requestIDContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	requestID string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx requestIDContext) Value(key any) any { | ||||||
|  | 	if key == requestIDKey { | ||||||
|  | 		return ctx.requestID | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // OutgoingPublicKeyID returns the public key ID (URI) associated with context. This | // OutgoingPublicKeyID returns the public key ID (URI) associated with context. This | ||||||
|  | @ -86,7 +107,19 @@ func OutgoingPublicKeyID(ctx context.Context) string { | ||||||
| // SetOutgoingPublicKeyID stores the given public key ID value and returns the wrapped | // SetOutgoingPublicKeyID stores the given public key ID value and returns the wrapped | ||||||
| // context. See PublicKeyID() for further information on the public key ID value. | // context. See PublicKeyID() for further information on the public key ID value. | ||||||
| func SetOutgoingPublicKeyID(ctx context.Context, id string) context.Context { | func SetOutgoingPublicKeyID(ctx context.Context, id string) context.Context { | ||||||
| 	return context.WithValue(ctx, outgoingPubKeyIDKey, id) | 	return outgoingPublicKeyIDKeyContext{Context: ctx, pubKeyID: id} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type outgoingPublicKeyIDKeyContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	pubKeyID string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx outgoingPublicKeyIDKeyContext) Value(key any) any { | ||||||
|  | 	if key == outgoingPubKeyIDKey { | ||||||
|  | 		return ctx.pubKeyID | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ReceivingAccount returns the local account who owns the resource being | // ReceivingAccount returns the local account who owns the resource being | ||||||
|  | @ -99,7 +132,19 @@ func ReceivingAccount(ctx context.Context) *gtsmodel.Account { | ||||||
| // SetReceivingAccount stores the given receiving account value and returns the wrapped | // SetReceivingAccount stores the given receiving account value and returns the wrapped | ||||||
| // context. See ReceivingAccount() for further information on the receiving account value. | // context. See ReceivingAccount() for further information on the receiving account value. | ||||||
| func SetReceivingAccount(ctx context.Context, acct *gtsmodel.Account) context.Context { | func SetReceivingAccount(ctx context.Context, acct *gtsmodel.Account) context.Context { | ||||||
| 	return context.WithValue(ctx, receivingAccountKey, acct) | 	return receivingAccountContext{Context: ctx, account: acct} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type receivingAccountContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	account *gtsmodel.Account | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx receivingAccountContext) Value(key any) any { | ||||||
|  | 	if key == receivingAccountKey { | ||||||
|  | 		return ctx.account | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RequestingAccount returns the remote account interacting with a local | // RequestingAccount returns the remote account interacting with a local | ||||||
|  | @ -112,7 +157,19 @@ func RequestingAccount(ctx context.Context) *gtsmodel.Account { | ||||||
| // SetRequestingAccount stores the given requesting account value and returns the wrapped | // SetRequestingAccount stores the given requesting account value and returns the wrapped | ||||||
| // context. See RequestingAccount() for further information on the requesting account value. | // context. See RequestingAccount() for further information on the requesting account value. | ||||||
| func SetRequestingAccount(ctx context.Context, acct *gtsmodel.Account) context.Context { | func SetRequestingAccount(ctx context.Context, acct *gtsmodel.Account) context.Context { | ||||||
| 	return context.WithValue(ctx, requestingAccountKey, acct) | 	return requestingAccountContext{Context: ctx, account: acct} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type requestingAccountContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	account *gtsmodel.Account | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx requestingAccountContext) Value(key any) any { | ||||||
|  | 	if key == requestingAccountKey { | ||||||
|  | 		return ctx.account | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // OtherIRIs returns other IRIs which are involved in the current ActivityPub request | // OtherIRIs returns other IRIs which are involved in the current ActivityPub request | ||||||
|  | @ -126,7 +183,19 @@ func OtherIRIs(ctx context.Context) []*url.URL { | ||||||
| // SetOtherIRIs stores the given IRIs slice and returns the wrapped context. | // SetOtherIRIs stores the given IRIs slice and returns the wrapped context. | ||||||
| // See OtherIRIs() for further information on the IRIs slice value. | // See OtherIRIs() for further information on the IRIs slice value. | ||||||
| func SetOtherIRIs(ctx context.Context, iris []*url.URL) context.Context { | func SetOtherIRIs(ctx context.Context, iris []*url.URL) context.Context { | ||||||
| 	return context.WithValue(ctx, otherIRIsKey, iris) | 	return otherIRIsContext{Context: ctx, iris: iris} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type otherIRIsContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	iris []*url.URL | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx otherIRIsContext) Value(key any) any { | ||||||
|  | 	if key == otherIRIsKey { | ||||||
|  | 		return ctx.iris | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // HTTPClientSignFunc returns an httpclient signing function for the current client | // HTTPClientSignFunc returns an httpclient signing function for the current client | ||||||
|  | @ -139,7 +208,19 @@ func HTTPClientSignFunc(ctx context.Context) func(*http.Request) error { | ||||||
| // SetHTTPClientSignFunc stores the given httpclient signing function and returns the wrapped | // SetHTTPClientSignFunc stores the given httpclient signing function and returns the wrapped | ||||||
| // context. See HTTPClientSignFunc() for further information on the signing function value. | // context. See HTTPClientSignFunc() for further information on the signing function value. | ||||||
| func SetHTTPClientSignFunc(ctx context.Context, fn func(*http.Request) error) context.Context { | func SetHTTPClientSignFunc(ctx context.Context, fn func(*http.Request) error) context.Context { | ||||||
| 	return context.WithValue(ctx, httpClientSignFnKey, fn) | 	return httpClientSignFuncContext{Context: ctx, signfn: fn} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type httpClientSignFuncContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	signfn func(*http.Request) error | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx httpClientSignFuncContext) Value(key any) any { | ||||||
|  | 	if key == httpClientSignFnKey { | ||||||
|  | 		return ctx.signfn | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // HTTPSignatureVerifier returns an http signature verifier for the current ActivityPub | // HTTPSignatureVerifier returns an http signature verifier for the current ActivityPub | ||||||
|  | @ -152,7 +233,19 @@ func HTTPSignatureVerifier(ctx context.Context) httpsig.VerifierWithOptions { | ||||||
| // SetHTTPSignatureVerifier stores the given http signature verifier and returns the | // SetHTTPSignatureVerifier stores the given http signature verifier and returns the | ||||||
| // wrapped context. See HTTPSignatureVerifier() for further information on the verifier value. | // wrapped context. See HTTPSignatureVerifier() for further information on the verifier value. | ||||||
| func SetHTTPSignatureVerifier(ctx context.Context, verifier httpsig.VerifierWithOptions) context.Context { | func SetHTTPSignatureVerifier(ctx context.Context, verifier httpsig.VerifierWithOptions) context.Context { | ||||||
| 	return context.WithValue(ctx, httpSigVerifierKey, verifier) | 	return httpSignatureVerifierContext{Context: ctx, verifier: verifier} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type httpSignatureVerifierContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	verifier httpsig.VerifierWithOptions | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx httpSignatureVerifierContext) Value(key any) any { | ||||||
|  | 	if key == httpSigVerifierKey { | ||||||
|  | 		return ctx.verifier | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // HTTPSignature returns the http signature string | // HTTPSignature returns the http signature string | ||||||
|  | @ -165,7 +258,19 @@ func HTTPSignature(ctx context.Context) string { | ||||||
| // SetHTTPSignature stores the given http signature string and returns the wrapped | // SetHTTPSignature stores the given http signature string and returns the wrapped | ||||||
| // context. See HTTPSignature() for further information on the verifier value. | // context. See HTTPSignature() for further information on the verifier value. | ||||||
| func SetHTTPSignature(ctx context.Context, signature string) context.Context { | func SetHTTPSignature(ctx context.Context, signature string) context.Context { | ||||||
| 	return context.WithValue(ctx, httpSigKey, signature) | 	return httpSignatureContext{Context: ctx, signature: signature} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type httpSignatureContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	signature string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx httpSignatureContext) Value(key any) any { | ||||||
|  | 	if key == httpSigKey { | ||||||
|  | 		return ctx.signature | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // HTTPSignaturePubKeyID returns the public key id of the http signature | // HTTPSignaturePubKeyID returns the public key id of the http signature | ||||||
|  | @ -178,7 +283,19 @@ func HTTPSignaturePubKeyID(ctx context.Context) *url.URL { | ||||||
| // SetHTTPSignaturePubKeyID stores the given http signature public key id and returns | // SetHTTPSignaturePubKeyID stores the given http signature public key id and returns | ||||||
| // the wrapped context. See HTTPSignaturePubKeyID() for further information on the value. | // the wrapped context. See HTTPSignaturePubKeyID() for further information on the value. | ||||||
| func SetHTTPSignaturePubKeyID(ctx context.Context, pubKeyID *url.URL) context.Context { | func SetHTTPSignaturePubKeyID(ctx context.Context, pubKeyID *url.URL) context.Context { | ||||||
| 	return context.WithValue(ctx, httpSigPubKeyIDKey, pubKeyID) | 	return httpSigPubKeyIDContext{Context: ctx, pubKeyID: pubKeyID} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type httpSigPubKeyIDContext struct { | ||||||
|  | 	context.Context | ||||||
|  | 	pubKeyID *url.URL | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ctx httpSigPubKeyIDContext) Value(key any) any { | ||||||
|  | 	if key == httpSigPubKeyIDKey { | ||||||
|  | 		return ctx.pubKeyID | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // IsFastFail returns whether the "fastfail" context key has been set. This | // IsFastFail returns whether the "fastfail" context key has been set. This | ||||||
|  | @ -192,7 +309,16 @@ func IsFastfail(ctx context.Context) bool { | ||||||
| // SetFastFail sets the "fastfail" context flag and returns this wrapped context. | // SetFastFail sets the "fastfail" context flag and returns this wrapped context. | ||||||
| // See IsFastFail() for further information on the "fastfail" context flag. | // See IsFastFail() for further information on the "fastfail" context flag. | ||||||
| func SetFastFail(ctx context.Context) context.Context { | func SetFastFail(ctx context.Context) context.Context { | ||||||
| 	return context.WithValue(ctx, fastFailKey, struct{}{}) | 	return fastFailContext{ctx} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type fastFailContext struct{ context.Context } | ||||||
|  | 
 | ||||||
|  | func (ctx fastFailContext) Value(key any) any { | ||||||
|  | 	if key == fastFailKey { | ||||||
|  | 		return struct{}{} | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Barebones returns whether the "barebones" context key has been set. This | // Barebones returns whether the "barebones" context key has been set. This | ||||||
|  | @ -206,5 +332,14 @@ func Barebones(ctx context.Context) bool { | ||||||
| // SetBarebones sets the "barebones" context flag and returns this wrapped context. | // SetBarebones sets the "barebones" context flag and returns this wrapped context. | ||||||
| // See Barebones() for further information on the "barebones" context flag. | // See Barebones() for further information on the "barebones" context flag. | ||||||
| func SetBarebones(ctx context.Context) context.Context { | func SetBarebones(ctx context.Context) context.Context { | ||||||
| 	return context.WithValue(ctx, barebonesKey, struct{}{}) | 	return barebonesContext{ctx} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type barebonesContext struct{ context.Context } | ||||||
|  | 
 | ||||||
|  | func (ctx barebonesContext) Value(key any) any { | ||||||
|  | 	if key == barebonesKey { | ||||||
|  | 		return struct{}{} | ||||||
|  | 	} | ||||||
|  | 	return ctx.Context.Value(key) | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										53
									
								
								internal/gtscontext/context_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								internal/gtscontext/context_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | ||||||
|  | package gtscontext_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"net/url" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/gtscontext" | ||||||
|  | 	"code.superseriousbusiness.org/gotosocial/internal/gtsmodel" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func BenchmarkContexts(b *testing.B) { | ||||||
|  | 	var receiving *gtsmodel.Account | ||||||
|  | 	var requesting *gtsmodel.Account | ||||||
|  | 	var otherIRIs []*url.URL | ||||||
|  | 
 | ||||||
|  | 	b.RunParallel(func(pb *testing.PB) { | ||||||
|  | 		for pb.Next() { | ||||||
|  | 			ctx := context.Background() | ||||||
|  | 
 | ||||||
|  | 			ctx = gtscontext.SetBarebones(ctx) | ||||||
|  | 			ctx = gtscontext.SetFastFail(ctx) | ||||||
|  | 			ctx = gtscontext.SetDryRun(ctx) | ||||||
|  | 			ctx = gtscontext.SetReceivingAccount(ctx, receiving) | ||||||
|  | 			ctx = gtscontext.SetRequestingAccount(ctx, requesting) | ||||||
|  | 			ctx = gtscontext.SetOtherIRIs(ctx, otherIRIs) | ||||||
|  | 
 | ||||||
|  | 			if !gtscontext.Barebones(ctx) { | ||||||
|  | 				println("oh no!") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if !gtscontext.IsFastfail(ctx) { | ||||||
|  | 				println("oh no!") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if !gtscontext.DryRun(ctx) { | ||||||
|  | 				println("oh no!") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if gtscontext.ReceivingAccount(ctx) != nil { | ||||||
|  | 				println("oh no!") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if gtscontext.RequestingAccount(ctx) != nil { | ||||||
|  | 				println("oh no!") | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(gtscontext.OtherIRIs(ctx)) > 0 { | ||||||
|  | 				println("oh no!") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue