Skip to content

Optimize attribute slice conversion#8039

Merged
MrAlias merged 10 commits intoopen-telemetry:mainfrom
MrAlias:attr-slice
Mar 12, 2026
Merged

Optimize attribute slice conversion#8039
MrAlias merged 10 commits intoopen-telemetry:mainfrom
MrAlias:attr-slice

Conversation

@MrAlias
Copy link
Copy Markdown
Contributor

@MrAlias MrAlias commented Mar 10, 2026

This PR refactors the internal slice conversion helpers in attribute/internal to use generics and adds explicit short-length fast paths for slice-backed attribute values.

The main changes are:

  • replace the old type-specific internal helpers with generic SliceValue[T] and AsSlice[T]
  • preserve the comparable-array storage model used by Value and KeyValue
  • add fixed-size fast paths for lengths 0..3
  • add a matching short-length fast path in IntSliceValue before falling back to []int64 conversion
  • keep reflection as the fallback for larger slice lengths
  • keep the existing Value.As*Slice() methods in attribute as thin wrappers over the generic internal helpers
  • add type-mismatch coverage for AsSlice[T]
  • expand the benchmark suite so it measures both:
    • short slices that hit the new fixed-size path
    • longer slices that still use the reflective fallback

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 without reflect.ArrayOf.

The fast path is intentionally small. The cutoff of 0..3 is based on a combination of:

  • benchmark gains for short slices
  • semantic convention examples where 1..3 values are common
  • downstream source analysis, which found that most external call sites pass variables rather than inline literals, so static source scanning does not justify a larger cutoff

External Usage Validation

Downstream scan:

  • sampled repos cloned and scanned: 285
  • most external call sites pass dynamic slice variables, not inline literals
  • final literal counts recovered statically:
    • StringSlice: total 131, dynamic 121, literal len 0: 1, len 1: 8, len 2: 1
    • IntSlice: total 5, all dynamic
    • Int64Slice: total 6, all dynamic
    • BoolSlice: total 3, all dynamic
    • Float64Slice: total 3, all dynamic

Semantic conventions reviewed locally in semantic-conventions include several slice-valued attributes where short lists are normal, for example:

  • browser.brands
  • gen_ai.request.stop_sequences
  • gen_ai.request.encoding_formats
  • gen_ai.response.finish_reasons
  • user.roles
  • file.attributes

There 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:

  • internal microbenchmarks now run both Len2 and Len8
  • public attribute benchmarks now run both Len2 and Len8
  • repeated benchmark runs were compared with benchstat

Len2 exercises the new fixed-size path. Len8 exercises the reflective fallback path.

Headline result:

  • short slices improve substantially
  • large slices stay close to baseline because they still use reflection
  • IntSlice now gets the same short-slice win as the other slice types, but larger []int values still pay the []int to []int64 conversion cost

Internal Helpers

goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/attribute/internal
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
                         │ /tmp/bench-internal-base-v2.txt │  /tmp/bench-internal-current-v2.txt  │
                         │             sec/op              │    sec/op     vs base                │
BoolSliceValue/Len2-8                        124.00n ±  4%   19.36n ± 12%  -84.39% (p=0.000 n=12)
BoolSliceValue/Len8-8                         129.9n ±  2%   136.8n ±  3%   +5.31% (p=0.000 n=12)
Int64SliceValue/Len2-8                       146.00n ± 26%   26.13n ±  3%  -82.10% (p=0.000 n=12)
Int64SliceValue/Len8-8                        172.1n ±  4%   174.9n ±  5%        ~ (p=0.443 n=12)
Float64SliceValue/Len2-8                     151.70n ±  2%   26.25n ±  2%  -82.70% (p=0.000 n=12)
Float64SliceValue/Len8-8                      173.6n ±  2%   169.7n ±  4%        ~ (p=0.155 n=12)
StringSliceValue/Len2-8                      177.15n ±  2%   42.03n ±  3%  -76.27% (p=0.000 n=12)
StringSliceValue/Len8-8                       217.2n ±  6%   219.1n ±  6%        ~ (p=0.504 n=12)
AsFloat64Slice/Len2-8                         96.77n ±  3%   63.05n ± 27%  -34.84% (p=0.000 n=12)
AsFloat64Slice/Len8-8                         123.8n ± 18%   117.1n ±  4%        ~ (p=1.000 n=12)
geomean                                       147.6n         71.85n        -51.33%

                         │ /tmp/bench-internal-base-v2.txt │  /tmp/bench-internal-current-v2.txt  │
                         │              B/op               │    B/op     vs base                  │
BoolSliceValue/Len2-8                           4.000 ± 0%   2.000 ± 0%  -50.00% (p=0.000 n=12)
BoolSliceValue/Len8-8                           16.00 ± 0%   16.00 ± 0%        ~ (p=1.000 n=12) ¹
Int64SliceValue/Len2-8                          32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
Int64SliceValue/Len8-8                          128.0 ± 0%   128.0 ± 0%        ~ (p=1.000 n=12) ¹
Float64SliceValue/Len2-8                        32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
Float64SliceValue/Len8-8                        128.0 ± 0%   128.0 ± 0%        ~ (p=1.000 n=12) ¹
StringSliceValue/Len2-8                         64.00 ± 0%   32.00 ± 0%  -50.00% (p=0.000 n=12)
StringSliceValue/Len8-8                         256.0 ± 0%   256.0 ± 0%        ~ (p=1.000 n=12) ¹
AsFloat64Slice/Len2-8                           40.00 ± 0%   40.00 ± 0%        ~ (p=1.000 n=12) ¹
AsFloat64Slice/Len8-8                           88.00 ± 0%   88.00 ± 0%        ~ (p=1.000 n=12) ¹
geomean                                         47.77        36.21       -24.21%
¹ all samples are equal

                         │ /tmp/bench-internal-base-v2.txt │  /tmp/bench-internal-current-v2.txt  │
                         │            allocs/op            │ allocs/op   vs base                  │
BoolSliceValue/Len2-8                           2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
BoolSliceValue/Len8-8                           2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
Int64SliceValue/Len2-8                          2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
Int64SliceValue/Len8-8                          2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
Float64SliceValue/Len2-8                        2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
Float64SliceValue/Len8-8                        2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
StringSliceValue/Len2-8                         2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
StringSliceValue/Len8-8                         2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
AsFloat64Slice/Len2-8                           2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
AsFloat64Slice/Len8-8                           2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
geomean                                         2.000        1.516       -24.21%
¹ all samples are equal

Public attribute API

goos: linux
goarch: amd64
pkg: go.opentelemetry.io/otel/attribute
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
                                   │ /tmp/bench-attr-base-v2.txt │    /tmp/bench-attr-current-v3.txt    │
                                   │           sec/op            │    sec/op     vs base                │
BoolSlice/Len2/Value-8                             130.65n ±  3%   16.90n ±  3%  -87.06% (p=0.000 n=12)
BoolSlice/Len2/KeyValue-8                          135.85n ± 14%   23.49n ±  2%  -82.71% (p=0.000 n=12)
BoolSlice/Len2/AsBoolSlice-8                        50.22n ±  1%   19.12n ± 15%  -61.92% (p=0.000 n=12)
BoolSlice/Len2/Emit-8                               343.9n ±  1%   293.4n ±  1%  -14.68% (p=0.000 n=12)
BoolSlice/Len8/Value-8                              134.7n ±  2%   136.4n ±  2%   +1.22% (p=0.011 n=12)
BoolSlice/Len8/KeyValue-8                           137.7n ±  2%   140.6n ±  2%   +2.11% (p=0.046 n=12)
BoolSlice/Len8/AsBoolSlice-8                        53.59n ± 22%   61.15n ± 22%  +14.11% (p=0.020 n=12)
BoolSlice/Len8/Emit-8                               773.5n ±  1%   788.5n ±  2%   +1.93% (p=0.028 n=12)
IntSlice/Len2/Value-8                              140.85n ±  2%   36.20n ±  3%  -74.30% (p=0.000 n=12)
IntSlice/Len2/KeyValue-8                           149.65n ±  2%   42.84n ±  3%  -71.37% (p=0.000 n=12)
IntSlice/Len2/Emit-8                                318.2n ±  3%   279.6n ± 15%  -12.15% (p=0.012 n=12)
IntSlice/Len8/Value-8                               217.9n ±  1%   228.8n ±  2%   +5.00% (p=0.001 n=12)
IntSlice/Len8/KeyValue-8                            225.6n ± 29%   232.3n ±  3%        ~ (p=0.767 n=12)
IntSlice/Len8/Emit-8                                480.0n ±  1%   478.6n ±  2%        ~ (p=0.899 n=12)
Int64Slice/Len2/Value-8                            150.90n ±  1%   27.43n ±  3%  -81.82% (p=0.000 n=12)
Int64Slice/Len2/KeyValue-8                         152.05n ±  1%   34.34n ±  3%  -77.42% (p=0.000 n=12)
Int64Slice/Len2/AsInt64Slice-8                      58.69n ±  4%   28.58n ±  3%  -51.30% (p=0.000 n=12)
Int64Slice/Len2/Emit-8                              318.4n ±  3%   273.9n ±  1%  -13.99% (p=0.000 n=12)
Int64Slice/Len8/Value-8                             173.0n ±  8%   177.8n ±  2%        ~ (p=0.173 n=12)
Int64Slice/Len8/KeyValue-8                          184.0n ± 24%   184.3n ±  2%        ~ (p=0.701 n=12)
Int64Slice/Len8/AsInt64Slice-8                      72.04n ±  2%   83.05n ±  2%  +15.30% (p=0.000 n=12)
Int64Slice/Len8/Emit-8                              474.9n ± 19%   501.9n ± 18%   +5.67% (p=0.020 n=12)
Float64Slice/Len2/Value-8                          150.95n ±  3%   26.92n ±  3%  -82.17% (p=0.000 n=12)
Float64Slice/Len2/KeyValue-8                       153.95n ±  3%   33.08n ±  2%  -78.52% (p=0.000 n=12)
Float64Slice/Len2/AsFloat64Slice-8                  60.31n ± 24%   27.02n ±  1%  -55.19% (p=0.000 n=12)
Float64Slice/Len2/Emit-8                            434.2n ±  2%   380.4n ±  1%  -12.40% (p=0.000 n=12)
Float64Slice/Len8/Value-8                           173.7n ±  2%   175.2n ± 25%        ~ (p=0.248 n=12)
Float64Slice/Len8/KeyValue-8                        174.0n ±  4%   175.2n ±  2%        ~ (p=0.702 n=12)
Float64Slice/Len8/AsFloat64Slice-8                  71.17n ±  8%   78.00n ±  4%   +9.58% (p=0.007 n=12)
Float64Slice/Len8/Emit-8                            920.6n ± 20%   909.9n ±  8%        ~ (p=0.378 n=12)
StringSlice/Len2/Value-8                           174.00n ±  5%   41.97n ± 20%  -75.88% (p=0.000 n=12)
StringSlice/Len2/KeyValue-8                        179.85n ±  2%   46.76n ±  2%  -74.00% (p=0.000 n=12)
StringSlice/Len2/AsStringSlice-8                    76.03n ±  5%   39.83n ±  3%  -47.61% (p=0.000 n=12)
StringSlice/Len2/Emit-8                             386.0n ±  2%   332.3n ±  3%  -13.91% (p=0.000 n=12)
StringSlice/Len8/Value-8                            225.4n ±  4%   226.2n ±  4%        ~ (p=0.311 n=12)
StringSlice/Len8/KeyValue-8                         228.1n ±  7%   234.2n ±  4%        ~ (p=0.386 n=12)
StringSlice/Len8/AsStringSlice-8                    110.8n ± 25%   117.8n ±  4%        ~ (p=0.173 n=12)
StringSlice/Len8/Emit-8                             658.7n ±  3%   647.9n ±  5%        ~ (p=0.319 n=12)
geomean                                             181.7n         110.7n        -39.08%

                                   │ /tmp/bench-attr-base-v2.txt │    /tmp/bench-attr-current-v3.txt    │
                                   │            B/op             │    B/op     vs base                  │
BoolSlice/Len2/Value-8                                4.000 ± 0%   2.000 ± 0%  -50.00% (p=0.000 n=12)
BoolSlice/Len2/KeyValue-8                             4.000 ± 0%   2.000 ± 0%  -50.00% (p=0.000 n=12)
BoolSlice/Len2/AsBoolSlice-8                          2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len2/Emit-8                                 40.00 ± 0%   40.00 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/Value-8                                16.00 ± 0%   16.00 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/KeyValue-8                             16.00 ± 0%   16.00 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/AsBoolSlice-8                          8.000 ± 0%   8.000 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/Emit-8                                 88.00 ± 0%   88.00 ± 0%        ~ (p=1.000 n=12) ¹
IntSlice/Len2/Value-8                                 32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
IntSlice/Len2/KeyValue-8                              32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
IntSlice/Len2/Emit-8                                  56.00 ± 0%   56.00 ± 0%        ~ (p=1.000 n=12) ¹
IntSlice/Len8/Value-8                                 128.0 ± 0%   192.0 ± 0%  +50.00% (p=0.000 n=12)
IntSlice/Len8/KeyValue-8                              128.0 ± 0%   192.0 ± 0%  +50.00% (p=0.000 n=12)
IntSlice/Len8/Emit-8                                  136.0 ± 0%   136.0 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len2/Value-8                               32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
Int64Slice/Len2/KeyValue-8                            32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
Int64Slice/Len2/AsInt64Slice-8                        16.00 ± 0%   16.00 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len2/Emit-8                                56.00 ± 0%   56.00 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/Value-8                               128.0 ± 0%   128.0 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/KeyValue-8                            128.0 ± 0%   128.0 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/AsInt64Slice-8                        64.00 ± 0%   64.00 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/Emit-8                                136.0 ± 0%   136.0 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len2/Value-8                             32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
Float64Slice/Len2/KeyValue-8                          32.00 ± 0%   16.00 ± 0%  -50.00% (p=0.000 n=12)
Float64Slice/Len2/AsFloat64Slice-8                    16.00 ± 0%   16.00 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len2/Emit-8                              56.00 ± 0%   56.00 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/Value-8                             128.0 ± 0%   128.0 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/KeyValue-8                          128.0 ± 0%   128.0 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/AsFloat64Slice-8                    64.00 ± 0%   64.00 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/Emit-8                              136.0 ± 0%   136.0 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len2/Value-8                              64.00 ± 0%   32.00 ± 0%  -50.00% (p=0.000 n=12)
StringSlice/Len2/KeyValue-8                           64.00 ± 0%   32.00 ± 0%  -50.00% (p=0.000 n=12)
StringSlice/Len2/AsStringSlice-8                      32.00 ± 0%   32.00 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len2/Emit-8                               120.0 ± 0%   120.0 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/Value-8                              256.0 ± 0%   256.0 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/KeyValue-8                           256.0 ± 0%   256.0 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/AsStringSlice-8                      128.0 ± 0%   128.0 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/Emit-8                               344.0 ± 0%   344.0 ± 0%        ~ (p=1.000 n=12) ¹
geomean                                               49.39        42.05       -14.88%
¹ all samples are equal

                                   │ /tmp/bench-attr-base-v2.txt │    /tmp/bench-attr-current-v3.txt    │
                                   │          allocs/op          │ allocs/op   vs base                  │
BoolSlice/Len2/Value-8                                2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
BoolSlice/Len2/KeyValue-8                             2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
BoolSlice/Len2/AsBoolSlice-8                          1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len2/Emit-8                                 5.000 ± 0%   5.000 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/Value-8                                2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/KeyValue-8                             2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/AsBoolSlice-8                          1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
BoolSlice/Len8/Emit-8                                 11.00 ± 0%   11.00 ± 0%        ~ (p=1.000 n=12) ¹
IntSlice/Len2/Value-8                                 2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
IntSlice/Len2/KeyValue-8                              2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
IntSlice/Len2/Emit-8                                  4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
IntSlice/Len8/Value-8                                 2.000 ± 0%   3.000 ± 0%  +50.00% (p=0.000 n=12)
IntSlice/Len8/KeyValue-8                              2.000 ± 0%   3.000 ± 0%  +50.00% (p=0.000 n=12)
IntSlice/Len8/Emit-8                                  4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len2/Value-8                               2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
Int64Slice/Len2/KeyValue-8                            2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
Int64Slice/Len2/AsInt64Slice-8                        1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len2/Emit-8                                4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/Value-8                               2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/KeyValue-8                            2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/AsInt64Slice-8                        1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
Int64Slice/Len8/Emit-8                                4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len2/Value-8                             2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
Float64Slice/Len2/KeyValue-8                          2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
Float64Slice/Len2/AsFloat64Slice-8                    1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len2/Emit-8                              4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/Value-8                             2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/KeyValue-8                          2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/AsFloat64Slice-8                    1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
Float64Slice/Len8/Emit-8                              4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len2/Value-8                              2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
StringSlice/Len2/KeyValue-8                           2.000 ± 0%   1.000 ± 0%  -50.00% (p=0.000 n=12)
StringSlice/Len2/AsStringSlice-8                      1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len2/Emit-8                               4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/Value-8                              2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/KeyValue-8                           2.000 ± 0%   2.000 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/AsStringSlice-8                      1.000 ± 0%   1.000 ± 0%        ~ (p=1.000 n=12) ¹
StringSlice/Len8/Emit-8                               4.000 ± 0%   4.000 ± 0%        ~ (p=1.000 n=12) ¹
geomean                                               2.143        1.824       -14.88%
¹ all samples are equal

Notes

  • Len2 is where the wins show up because those calls avoid reflect.ArrayOf and one allocation.
  • Len8 stays much closer to baseline because it still uses the reflective path.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 11, 2026

Codecov Report

❌ Patch coverage is 88.67925% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.6%. Comparing base (6d79ac3) to head (a4e0898).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
attribute/internal/attribute.go 86.2% 4 Missing ⚠️
attribute/value.go 91.6% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@          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     
Files with missing lines Coverage Δ
attribute/value.go 91.9% <91.6%> (-1.0%) ⬇️
attribute/internal/attribute.go 88.2% <86.2%> (+4.9%) ⬆️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MrAlias MrAlias marked this pull request as ready for review March 11, 2026 17:41
Copilot AI review requested due to automatic review settings March 11, 2026 17:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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] and AsSlice[T], with 0..3 fast paths and reflection fallback.
  • Update attribute.Value slice 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.

MrAlias and others added 2 commits March 11, 2026 10:53
Fix BenchmarkStringSlice key name

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@MrAlias MrAlias added this to the v1.42.0 milestone Mar 11, 2026
Copy link
Copy Markdown
Contributor

@dashpole dashpole left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice.

@pellared pellared modified the milestones: v1.42.0, v1.43.0 Mar 12, 2026
@MrAlias MrAlias merged commit 9587c57 into open-telemetry:main Mar 12, 2026
34 checks passed
@MrAlias MrAlias deleted the attr-slice branch March 12, 2026 16:33
dmathieu added a commit that referenced this pull request Apr 3, 2026
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>
pellared added a commit that referenced this pull request Apr 8, 2026
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
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants