You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2026-06-19 21:45:50 +02:00
Optimize attribute slice conversion (#8039)
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
```text
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
```text
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.
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: David Ashpole <dashpole@google.com>
This commit is contained in:
@@ -17,6 +17,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
### 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)
|
||||
|
||||
### Deprecated
|
||||
|
||||
|
||||
+149
-99
@@ -60,28 +60,38 @@ func BenchmarkBool(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkBoolSlice(b *testing.B) {
|
||||
k, v := "bool slice", []bool{true, false, true}
|
||||
kv := attribute.BoolSlice(k, v)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
v []bool
|
||||
}{
|
||||
{name: "Len2", v: []bool{true, false}},
|
||||
{name: "Len8", v: []bool{true, false, true, false, true, false, true, false}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
k, v := "bool slice", bench.v
|
||||
kv := attribute.BoolSlice(k, v)
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.BoolSliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.BoolSlice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsBoolSlice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outBoolSlice = kv.Value.AsBoolSlice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.BoolSliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.BoolSlice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsBoolSlice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outBoolSlice = kv.Value.AsBoolSlice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt(b *testing.B) {
|
||||
@@ -104,22 +114,32 @@ func BenchmarkInt(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkIntSlice(b *testing.B) {
|
||||
k, v := "int slice", []int{42, -3, 12}
|
||||
kv := attribute.IntSlice(k, v)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
v []int
|
||||
}{
|
||||
{name: "Len2", v: []int{42, -3}},
|
||||
{name: "Len8", v: []int{42, -3, 12, 7, 9, 11, -5, 0}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
k, v := "int slice", bench.v
|
||||
kv := attribute.IntSlice(k, v)
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.IntSliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.IntSlice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.IntSliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.IntSlice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt64(b *testing.B) {
|
||||
@@ -148,28 +168,38 @@ func BenchmarkInt64(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkInt64Slice(b *testing.B) {
|
||||
k, v := "int64 slice", []int64{42, -3, 12}
|
||||
kv := attribute.Int64Slice(k, v)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
v []int64
|
||||
}{
|
||||
{name: "Len2", v: []int64{42, -3}},
|
||||
{name: "Len8", v: []int64{42, -3, 12, 7, 9, 11, -5, 0}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
k, v := "int64 slice", bench.v
|
||||
kv := attribute.Int64Slice(k, v)
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.Int64SliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.Int64Slice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsInt64Slice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outInt64Slice = kv.Value.AsInt64Slice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.Int64SliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.Int64Slice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsInt64Slice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outInt64Slice = kv.Value.AsInt64Slice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFloat64(b *testing.B) {
|
||||
@@ -198,28 +228,38 @@ func BenchmarkFloat64(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkFloat64Slice(b *testing.B) {
|
||||
k, v := "float64 slice", []float64{42, -3, 12}
|
||||
kv := attribute.Float64Slice(k, v)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
v []float64
|
||||
}{
|
||||
{name: "Len2", v: []float64{42, -3}},
|
||||
{name: "Len8", v: []float64{42, -3, 12, 7, 9, 11, -5, 0}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
k, v := "float64 slice", bench.v
|
||||
kv := attribute.Float64Slice(k, v)
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.Float64SliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.Float64Slice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsFloat64Slice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outFloat64Slice = kv.Value.AsFloat64Slice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.Float64SliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.Float64Slice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsFloat64Slice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outFloat64Slice = kv.Value.AsFloat64Slice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkString(b *testing.B) {
|
||||
@@ -248,28 +288,38 @@ func BenchmarkString(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkStringSlice(b *testing.B) {
|
||||
k, v := "float64 slice", []string{"forty-two", "negative three", "twelve"}
|
||||
kv := attribute.StringSlice(k, v)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
v []string
|
||||
}{
|
||||
{name: "Len2", v: []string{"forty-two", "negative three"}},
|
||||
{name: "Len8", v: []string{"forty-two", "negative three", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen"}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
k, v := "string slice", bench.v
|
||||
kv := attribute.StringSlice(k, v)
|
||||
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.StringSliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.StringSlice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsStringSlice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outStrSlice = kv.Value.AsStringSlice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
b.Run("Value", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outV = attribute.StringSliceValue(v)
|
||||
}
|
||||
})
|
||||
b.Run("KeyValue", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outKV = attribute.StringSlice(k, v)
|
||||
}
|
||||
})
|
||||
b.Run("AsStringSlice", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
outStrSlice = kv.Value.AsStringSlice()
|
||||
}
|
||||
})
|
||||
b.Run("Emit", benchmarkEmit(kv))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSetEquals(b *testing.B) {
|
||||
|
||||
@@ -11,80 +11,63 @@ import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// BoolSliceValue converts a bool slice into an array with same elements as slice.
|
||||
func BoolSliceValue(v []bool) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[bool]())).Elem()
|
||||
// sliceElem is the exact set of element types stored in attribute slice values.
|
||||
// Using a closed set prevents accidental instantiations for unsupported types.
|
||||
type sliceElem interface {
|
||||
bool | int64 | float64 | string
|
||||
}
|
||||
|
||||
// SliceValue converts a slice into an array with the same elements.
|
||||
func SliceValue[T sliceElem](v []T) any {
|
||||
// Keep only the common tiny-slice cases out of reflection. Extending this
|
||||
// much further increases code size for diminishing benefit while larger
|
||||
// slices still need the generic reflective path to preserve comparability.
|
||||
// This matches the short lengths that show up most often in local
|
||||
// benchmarks and semantic convention examples while leaving larger, less
|
||||
// predictable slices on the generic reflective path.
|
||||
switch len(v) {
|
||||
case 0:
|
||||
return [0]T{}
|
||||
case 1:
|
||||
return [1]T{v[0]}
|
||||
case 2:
|
||||
return [2]T{v[0], v[1]}
|
||||
case 3:
|
||||
return [3]T{v[0], v[1], v[2]}
|
||||
}
|
||||
|
||||
return sliceValueReflect(v)
|
||||
}
|
||||
|
||||
// AsSlice converts an array into a slice with the same elements.
|
||||
func AsSlice[T sliceElem](v any) []T {
|
||||
// Mirror the small fixed-array fast path used by SliceValue.
|
||||
switch a := v.(type) {
|
||||
case [0]T:
|
||||
return []T{}
|
||||
case [1]T:
|
||||
return []T{a[0]}
|
||||
case [2]T:
|
||||
return []T{a[0], a[1]}
|
||||
case [3]T:
|
||||
return []T{a[0], a[1], a[2]}
|
||||
}
|
||||
|
||||
return asSliceReflect[T](v)
|
||||
}
|
||||
|
||||
func sliceValueReflect[T sliceElem](v []T) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[T]())).Elem()
|
||||
reflect.Copy(cp, reflect.ValueOf(v))
|
||||
return cp.Interface()
|
||||
}
|
||||
|
||||
// Int64SliceValue converts an int64 slice into an array with same elements as slice.
|
||||
func Int64SliceValue(v []int64) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[int64]())).Elem()
|
||||
reflect.Copy(cp, reflect.ValueOf(v))
|
||||
return cp.Interface()
|
||||
}
|
||||
|
||||
// Float64SliceValue converts a float64 slice into an array with same elements as slice.
|
||||
func Float64SliceValue(v []float64) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[float64]())).Elem()
|
||||
reflect.Copy(cp, reflect.ValueOf(v))
|
||||
return cp.Interface()
|
||||
}
|
||||
|
||||
// StringSliceValue converts a string slice into an array with same elements as slice.
|
||||
func StringSliceValue(v []string) any {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[string]())).Elem()
|
||||
reflect.Copy(cp, reflect.ValueOf(v))
|
||||
return cp.Interface()
|
||||
}
|
||||
|
||||
// AsBoolSlice converts a bool array into a slice into with same elements as array.
|
||||
func AsBoolSlice(v any) []bool {
|
||||
func asSliceReflect[T sliceElem](v any) []T {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Type().Kind() != reflect.Array {
|
||||
if !rv.IsValid() || rv.Kind() != reflect.Array || rv.Type().Elem() != reflect.TypeFor[T]() {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]bool, rv.Len())
|
||||
if len(cpy) > 0 {
|
||||
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// AsInt64Slice converts an int64 array into a slice into with same elements as array.
|
||||
func AsInt64Slice(v any) []int64 {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Type().Kind() != reflect.Array {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]int64, rv.Len())
|
||||
if len(cpy) > 0 {
|
||||
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// AsFloat64Slice converts a float64 array into a slice into with same elements as array.
|
||||
func AsFloat64Slice(v any) []float64 {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Type().Kind() != reflect.Array {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]float64, rv.Len())
|
||||
if len(cpy) > 0 {
|
||||
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// AsStringSlice converts a string array into a slice into with same elements as array.
|
||||
func AsStringSlice(v any) []string {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Type().Kind() != reflect.Array {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]string, rv.Len())
|
||||
cpy := make([]T, rv.Len())
|
||||
if len(cpy) > 0 {
|
||||
_ = reflect.Copy(reflect.ValueOf(cpy), rv)
|
||||
}
|
||||
|
||||
@@ -10,37 +10,37 @@ import (
|
||||
|
||||
var wrapFloat64SliceValue = func(v any) any {
|
||||
if vi, ok := v.([]float64); ok {
|
||||
return Float64SliceValue(vi)
|
||||
return SliceValue(vi)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var wrapInt64SliceValue = func(v any) any {
|
||||
if vi, ok := v.([]int64); ok {
|
||||
return Int64SliceValue(vi)
|
||||
return SliceValue(vi)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var wrapBoolSliceValue = func(v any) any {
|
||||
if vi, ok := v.([]bool); ok {
|
||||
return BoolSliceValue(vi)
|
||||
return SliceValue(vi)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var wrapStringSliceValue = func(v any) any {
|
||||
if vi, ok := v.([]string); ok {
|
||||
return StringSliceValue(vi)
|
||||
return SliceValue(vi)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
wrapAsBoolSlice = func(v any) any { return AsBoolSlice(v) }
|
||||
wrapAsInt64Slice = func(v any) any { return AsInt64Slice(v) }
|
||||
wrapAsFloat64Slice = func(v any) any { return AsFloat64Slice(v) }
|
||||
wrapAsStringSlice = func(v any) any { return AsStringSlice(v) }
|
||||
wrapAsBoolSlice = func(v any) any { return AsSlice[bool](v) }
|
||||
wrapAsInt64Slice = func(v any) any { return AsSlice[int64](v) }
|
||||
wrapAsFloat64Slice = func(v any) any { return AsSlice[float64](v) }
|
||||
wrapAsStringSlice = func(v any) any { return AsSlice[string](v) }
|
||||
)
|
||||
|
||||
func TestSliceValue(t *testing.T) {
|
||||
@@ -95,50 +95,112 @@ func TestSliceValue(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsSliceMismatchedType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fn func() any
|
||||
}{
|
||||
{name: "bool from int64 array", fn: func() any { return AsSlice[bool]([2]int64{1, 2}) }},
|
||||
{name: "int64 from float64 array", fn: func() any { return AsSlice[int64]([2]float64{1, 2}) }},
|
||||
{name: "float64 from string array", fn: func() any { return AsSlice[float64]([2]string{"1", "2"}) }},
|
||||
{name: "string from bool array", fn: func() any { return AsSlice[string]([2]bool{true, false}) }},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.fn()
|
||||
rv := reflect.ValueOf(got)
|
||||
if !rv.IsNil() {
|
||||
t.Fatalf("got %v, want nil", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// sync is a global used to ensure the benchmark are not optimized away.
|
||||
var sync any
|
||||
|
||||
func BenchmarkBoolSliceValue(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
s := []bool{true, false, true, false}
|
||||
|
||||
for b.Loop() {
|
||||
sync = BoolSliceValue(s)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
s []bool
|
||||
}{
|
||||
{name: "Len2", s: []bool{true, false}},
|
||||
{name: "Len8", s: []bool{true, false, true, false, true, false, true, false}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
sync = SliceValue(bench.s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInt64SliceValue(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
s := []int64{1, 2, 3, 4}
|
||||
|
||||
for b.Loop() {
|
||||
sync = Int64SliceValue(s)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
s []int64
|
||||
}{
|
||||
{name: "Len2", s: []int64{1, 2}},
|
||||
{name: "Len8", s: []int64{1, 2, 3, 4, 5, 6, 7, 8}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
sync = SliceValue(bench.s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFloat64SliceValue(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
s := []float64{1.2, 3.4, 5.6, 7.8}
|
||||
|
||||
for b.Loop() {
|
||||
sync = Float64SliceValue(s)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
s []float64
|
||||
}{
|
||||
{name: "Len2", s: []float64{1.2, 3.4}},
|
||||
{name: "Len8", s: []float64{1.2, 3.4, 5.6, 7.8, 9.1, 2.3, 4.5, 6.7}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
sync = SliceValue(bench.s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringSliceValue(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
s := []string{"a", "b", "c", "d"}
|
||||
|
||||
for b.Loop() {
|
||||
sync = StringSliceValue(s)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
s []string
|
||||
}{
|
||||
{name: "Len2", s: []string{"a", "b"}},
|
||||
{name: "Len8", s: []string{"a", "b", "c", "d", "e", "f", "g", "h"}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
sync = SliceValue(bench.s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAsFloat64Slice(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
var in any = [2]float64{1, 2.3}
|
||||
|
||||
for b.Loop() {
|
||||
sync = AsFloat64Slice(in)
|
||||
for _, bench := range []struct {
|
||||
name string
|
||||
in any
|
||||
}{
|
||||
{name: "Len2", in: [2]float64{1, 2.3}},
|
||||
{name: "Len8", in: [8]float64{1, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8, 8.9}},
|
||||
} {
|
||||
b.Run(bench.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for b.Loop() {
|
||||
sync = AsSlice[float64](bench.in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+30
-17
@@ -6,7 +6,6 @@ package attribute // import "go.opentelemetry.io/otel/attribute"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
attribute "go.opentelemetry.io/otel/attribute/internal"
|
||||
@@ -62,7 +61,7 @@ func BoolValue(v bool) Value {
|
||||
|
||||
// BoolSliceValue creates a BOOLSLICE Value.
|
||||
func BoolSliceValue(v []bool) Value {
|
||||
return Value{vtype: BOOLSLICE, slice: attribute.BoolSliceValue(v)}
|
||||
return Value{vtype: BOOLSLICE, slice: attribute.SliceValue(v)}
|
||||
}
|
||||
|
||||
// IntValue creates an INT64 Value.
|
||||
@@ -70,16 +69,30 @@ func IntValue(v int) Value {
|
||||
return Int64Value(int64(v))
|
||||
}
|
||||
|
||||
// IntSliceValue creates an INTSLICE Value.
|
||||
// IntSliceValue creates an INT64SLICE Value.
|
||||
func IntSliceValue(v []int) Value {
|
||||
cp := reflect.New(reflect.ArrayOf(len(v), reflect.TypeFor[int64]()))
|
||||
for i, val := range v {
|
||||
cp.Elem().Index(i).SetInt(int64(val))
|
||||
}
|
||||
return Value{
|
||||
vtype: INT64SLICE,
|
||||
slice: cp.Elem().Interface(),
|
||||
val := Value{vtype: INT64SLICE}
|
||||
|
||||
// Avoid the common tiny-slice cases from allocating a new slice.
|
||||
switch len(v) {
|
||||
case 0:
|
||||
val.slice = [0]int64{}
|
||||
case 1:
|
||||
val.slice = [1]int64{int64(v[0])}
|
||||
case 2:
|
||||
val.slice = [2]int64{int64(v[0]), int64(v[1])}
|
||||
case 3:
|
||||
val.slice = [3]int64{int64(v[0]), int64(v[1]), int64(v[2])}
|
||||
default:
|
||||
// Fallback to a new slice for larger slices.
|
||||
cp := make([]int64, len(v))
|
||||
for i, val := range v {
|
||||
cp[i] = int64(val)
|
||||
}
|
||||
val.slice = attribute.SliceValue(cp)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// Int64Value creates an INT64 Value.
|
||||
@@ -92,7 +105,7 @@ func Int64Value(v int64) Value {
|
||||
|
||||
// Int64SliceValue creates an INT64SLICE Value.
|
||||
func Int64SliceValue(v []int64) Value {
|
||||
return Value{vtype: INT64SLICE, slice: attribute.Int64SliceValue(v)}
|
||||
return Value{vtype: INT64SLICE, slice: attribute.SliceValue(v)}
|
||||
}
|
||||
|
||||
// Float64Value creates a FLOAT64 Value.
|
||||
@@ -105,7 +118,7 @@ func Float64Value(v float64) Value {
|
||||
|
||||
// Float64SliceValue creates a FLOAT64SLICE Value.
|
||||
func Float64SliceValue(v []float64) Value {
|
||||
return Value{vtype: FLOAT64SLICE, slice: attribute.Float64SliceValue(v)}
|
||||
return Value{vtype: FLOAT64SLICE, slice: attribute.SliceValue(v)}
|
||||
}
|
||||
|
||||
// StringValue creates a STRING Value.
|
||||
@@ -118,7 +131,7 @@ func StringValue(v string) Value {
|
||||
|
||||
// StringSliceValue creates a STRINGSLICE Value.
|
||||
func StringSliceValue(v []string) Value {
|
||||
return Value{vtype: STRINGSLICE, slice: attribute.StringSliceValue(v)}
|
||||
return Value{vtype: STRINGSLICE, slice: attribute.SliceValue(v)}
|
||||
}
|
||||
|
||||
// Type returns a type of the Value.
|
||||
@@ -142,7 +155,7 @@ func (v Value) AsBoolSlice() []bool {
|
||||
}
|
||||
|
||||
func (v Value) asBoolSlice() []bool {
|
||||
return attribute.AsBoolSlice(v.slice)
|
||||
return attribute.AsSlice[bool](v.slice)
|
||||
}
|
||||
|
||||
// AsInt64 returns the int64 value. Make sure that the Value's type is
|
||||
@@ -161,7 +174,7 @@ func (v Value) AsInt64Slice() []int64 {
|
||||
}
|
||||
|
||||
func (v Value) asInt64Slice() []int64 {
|
||||
return attribute.AsInt64Slice(v.slice)
|
||||
return attribute.AsSlice[int64](v.slice)
|
||||
}
|
||||
|
||||
// AsFloat64 returns the float64 value. Make sure that the Value's
|
||||
@@ -180,7 +193,7 @@ func (v Value) AsFloat64Slice() []float64 {
|
||||
}
|
||||
|
||||
func (v Value) asFloat64Slice() []float64 {
|
||||
return attribute.AsFloat64Slice(v.slice)
|
||||
return attribute.AsSlice[float64](v.slice)
|
||||
}
|
||||
|
||||
// AsString returns the string value. Make sure that the Value's type
|
||||
@@ -199,7 +212,7 @@ func (v Value) AsStringSlice() []string {
|
||||
}
|
||||
|
||||
func (v Value) asStringSlice() []string {
|
||||
return attribute.AsStringSlice(v.slice)
|
||||
return attribute.AsSlice[string](v.slice)
|
||||
}
|
||||
|
||||
type unknownValueType struct{}
|
||||
|
||||
Reference in New Issue
Block a user