Optimize attribute slice conversion#8039
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #8039 +/- ##
=====================================
Coverage 81.5% 81.6%
=====================================
Files 304 304
Lines 23448 23444 -4
=====================================
Hits 19132 19132
Misses 3928 3928
+ Partials 388 384 -4
🚀 New features to boost your workflow:
|
Scope generic type. Encapsulate the reflect functionality.
There was a problem hiding this comment.
Pull request overview
Refactors attribute/internal slice conversion utilities to use generics and adds fixed-size fast paths (lengths 0..3) for slice-backed attribute values, while keeping the comparable-array storage model required by attribute.Value/KeyValue. Updates public wrappers, tests, benchmarks, and changelog entry accordingly.
Changes:
- Replace type-specific internal helpers with generic
SliceValue[T]andAsSlice[T], with 0..3 fast paths and reflection fallback. - Update
attribute.Valueslice constructors/accessors to use the new generic internal helpers. - Expand internal + public benchmarks to cover both short (fast path) and longer (reflect fallback) slices; add mismatched-type test coverage.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| attribute/value.go | Switches slice storage/extraction to generic internal helpers; refactors IntSliceValue conversion. |
| attribute/internal/attribute.go | Introduces sliceElem, SliceValue[T], and AsSlice[T] with small-length fast paths and reflect fallback. |
| attribute/internal/attribute_test.go | Updates tests to generics; adds mismatched-type coverage; expands microbench coverage (Len2/Len8). |
| attribute/benchmark_test.go | Expands public benchmarks to run Len2 and Len8 variants for slice attribute APIs. |
| CHANGELOG.md | Adds an “Unreleased” entry describing the slice-handling optimization. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Fix BenchmarkStringSlice key name Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Release issue: #8127 ## Added - Add `IsRandom` and `WithRandom` on `TraceFlags`, and `IsRandom` on `SpanContext` in `go.opentelemetry.io/otel/trace` for [W3C Trace Context Level 2 Random Trace ID Flag](https://www.w3.org/TR/trace-context-2/#random-trace-id-flag) support. (#8012) - Add service detection with `WithService` in `go.opentelemetry.io/otel/sdk/resource`. (#7642) - Add `DefaultWithContext` and `EnvironmentWithContext` in `go.opentelemetry.io/otel/sdk/resource` to support plumbing `context.Context` through default and environment detectors. (#8051) - Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`. (#8038) - Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc`. (#8038) - Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`. (#8038) - Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#8038) - Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#8038) - Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#8038) - Support attributes with empty value (`attribute.EMPTY`) in `go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest`. (#8038) - Add support for per-series start time tracking for cumulative metrics in `go.opentelemetry.io/otel/sdk/metric`. Set `OTEL_GO_X_PER_SERIES_START_TIMESTAMPS=true` to enable. (#8060) - Add `WithCardinalityLimitSelector` for metric reader for configuring cardinality limits specific to the instrument kind. (#7855) ## Changed - Introduce the `EMPTY` Type in `go.opentelemetry.io/otel/attribute` to reflect that an empty value is now a valid value, with `INVALID` remaining as a deprecated alias of `EMPTY`. (#8038) - Refactor slice handling in `go.opentelemetry.io/otel/attribute` to optimize short slice values with fixed-size fast paths. (#8039) - Improve performance of span metric recording in `go.opentelemetry.io/otel/sdk/trace` by returning early if self-observability is not enabled. (#8067) - Improve formatting of metric data diffs in `go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest`. (#8073) ## Deprecated - Deprecate `INVALID` in `go.opentelemetry.io/otel/attribute`. Use `EMPTY` instead. (#8038) ## Fixed - Return spec-compliant `TraceIdRatioBased` description. This is a breaking behavioral change, but it is necessary to make the implementation [spec-compliant](https://opentelemetry.io/docs/specs/otel/trace/sdk/#traceidratiobased). (#8027) - Fix a race condition in `go.opentelemetry.io/otel/sdk/metric` where the lastvalue aggregation could collect the value 0 even when no zero-value measurements were recorded. (#8056) - Limit HTTP response body to 4 MiB in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp` to mitigate excessive memory usage caused by a misconfigured or malicious server. Responses exceeding the limit are treated as non-retryable errors. (#8108) - Limit HTTP response body to 4 MiB in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` to mitigate excessive memory usage caused by a misconfigured or malicious server. Responses exceeding the limit are treated as non-retryable errors. (#8108) - Limit HTTP response body to 4 MiB in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp` to mitigate excessive memory usage caused by a misconfigured or malicious server. Responses exceeding the limit are treated as non-retryable errors. (#8108) - `WithHostID` detector in `go.opentelemetry.io/otel/sdk/resource` to use full path for `kenv` command on BSD. (#8113) - Fix missing `request.GetBody` in `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp` to correctly handle HTTP2 GOAWAY frame. (#8096) --------- Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
Towards #7810 Fixes #8143 String representation follows: https://opentelemetry.io/docs/specs/otel/common/#anyvalue-representation-for-non-otlp-protocols This uses optimizations like #8039 and we inline the JSON-array/string encoding logic so we avoid the extra allocations and reflection overhead of marshaling through encoding/json (the code is inlined here not to reimplement JSON broadly, but to provide a spec-specific, allocation-conscious formatter for a constrained data model). Benchmarks of both `String` and `Emit` (that is going to be deprecated) showcase that `String` is even more efficient. ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/attribute cpu: 13th Gen Intel(R) Core(TM) i7-13800H BenchmarkBool/String-20 100000000 10.20 ns/op 0 B/op 0 allocs/op BenchmarkBool/Emit-20 100000000 10.33 ns/op 0 B/op 0 allocs/op BenchmarkBoolSlice/Len2/String-20 28427863 36.15 ns/op 16 B/op 1 allocs/op BenchmarkBoolSlice/Len2/Emit-20 5433291 201.8 ns/op 40 B/op 5 allocs/op BenchmarkBoolSlice/Len8/String-20 12453201 99.46 ns/op 48 B/op 1 allocs/op BenchmarkBoolSlice/Len8/Emit-20 2185160 546.0 ns/op 88 B/op 11 allocs/op BenchmarkInt/String-20 100000000 10.73 ns/op 0 B/op 0 allocs/op BenchmarkInt/Emit-20 100000000 11.03 ns/op 0 B/op 0 allocs/op BenchmarkIntSlice/Len2/String-20 17855926 61.57 ns/op 48 B/op 1 allocs/op BenchmarkIntSlice/Len2/Emit-20 6237072 184.9 ns/op 56 B/op 4 allocs/op BenchmarkIntSlice/Len8/String-20 6573506 192.1 ns/op 176 B/op 1 allocs/op BenchmarkIntSlice/Len8/Emit-20 3620901 332.8 ns/op 136 B/op 4 allocs/op BenchmarkInt64/String-20 100000000 10.90 ns/op 0 B/op 0 allocs/op BenchmarkInt64/Emit-20 100000000 10.91 ns/op 0 B/op 0 allocs/op BenchmarkInt64Slice/Len2/String-20 20924970 59.59 ns/op 48 B/op 1 allocs/op BenchmarkInt64Slice/Len2/Emit-20 6755516 184.2 ns/op 56 B/op 4 allocs/op BenchmarkInt64Slice/Len8/String-20 6033630 207.9 ns/op 176 B/op 1 allocs/op BenchmarkInt64Slice/Len8/Emit-20 3491808 327.2 ns/op 136 B/op 4 allocs/op BenchmarkFloat64/String-20 23607802 52.21 ns/op 2 B/op 1 allocs/op BenchmarkFloat64/Emit-20 13578472 93.34 ns/op 16 B/op 2 allocs/op BenchmarkFloat64Slice/Len2/String-20 12066591 111.0 ns/op 64 B/op 1 allocs/op BenchmarkFloat64Slice/Len2/Emit-20 5177293 234.3 ns/op 56 B/op 4 allocs/op BenchmarkFloat64Slice/Len8/String-20 3041408 381.9 ns/op 208 B/op 1 allocs/op BenchmarkFloat64Slice/Len8/Emit-20 2369974 548.3 ns/op 136 B/op 4 allocs/op BenchmarkString/String-20 137506468 8.578 ns/op 0 B/op 0 allocs/op BenchmarkString/Emit-20 139229646 8.542 ns/op 0 B/op 0 allocs/op BenchmarkStringSlice/Len2/Emit-20 5809321 228.9 ns/op 120 B/op 4 allocs/op BenchmarkStringSlice/Len8/String-20 5089977 240.0 ns/op 96 B/op 1 allocs/op BenchmarkStringSlice/Len8/Emit-20 2569848 480.0 ns/op 344 B/op 4 allocs/op BenchmarkByteSlice/String-20 32244670 34.31 ns/op 16 B/op 1 allocs/op BenchmarkByteSlice/Emit-20 36643321 34.63 ns/op 16 B/op 1 allocs/op ```
This PR refactors the internal slice conversion helpers in
attribute/internalto use generics and adds explicit short-length fast paths for slice-backed attribute values.The main changes are:
SliceValue[T]andAsSlice[T]ValueandKeyValue0..3IntSliceValuebefore falling back to[]int64conversionValue.As*Slice()methods inattributeas thin wrappers over the generic internal helpersAsSlice[T]Rationale
The package still needs reflection for arbitrary runtime lengths because slice values are stored as comparable arrays behind
any, and Go cannot construct a runtime-sized array type withoutreflect.ArrayOf.The fast path is intentionally small. The cutoff of
0..3is based on a combination of:1..3values are commonExternal Usage Validation
Downstream scan:
285StringSlice: total131, dynamic121, literal len0:1, len1:8, len2:1IntSlice: total5, all dynamicInt64Slice: total6, all dynamicBoolSlice: total3, all dynamicFloat64Slice: total3, all dynamicSemantic conventions reviewed locally in
semantic-conventionsinclude several slice-valued attributes where short lists are normal, for example:browser.brandsgen_ai.request.stop_sequencesgen_ai.request.encoding_formatsgen_ai.response.finish_reasonsuser.rolesfile.attributesThere are also clearly unbounded cases like headers, metadata, command args, and some cloud/provider arrays. That combination supports a small fast path, but not an assumption that all real-world slices are tiny.
Benchmarks
Changes to the benchmarks:
Len2andLen8attributebenchmarks now run bothLen2andLen8benchstatLen2exercises the new fixed-size path.Len8exercises the reflective fallback path.Headline result:
IntSlicenow gets the same short-slice win as the other slice types, but larger[]intvalues still pay the[]intto[]int64conversion costInternal Helpers
Public
attributeAPINotes
Len2is where the wins show up because those calls avoidreflect.ArrayOfand one allocation.Len8stays much closer to baseline because it still uses the reflective path.