| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package benchunit |
| |
| import ( |
| "strings" |
| "sync" |
| ) |
| |
| type tidyEntry struct { |
| tidied string |
| factor float64 |
| } |
| |
| var tidyCache sync.Map // unit string -> *tidyCache |
| |
| // Tidy normalizes a value with a (possibly pre-scaled) unit into base units. |
| // For example, if unit is "ns" or "MB", it will re-scale the value to |
| // "sec" or "B" units, respectively. It returns the re-scaled value and |
| // its new unit. If the value is already in base units, it does nothing. |
| func Tidy(value float64, unit string) (tidiedValue float64, tidiedUnit string) { |
| newUnit, factor := tidyUnit(unit) |
| return value * factor, newUnit |
| } |
| |
| // tidyUnit normalizes common pre-scaled units like "ns" to "sec" and |
| // "MB" to "B". It returns the tidied version of unit and the |
| // multiplicative factor to convert a value in unit "unit" to a value in |
| // unit "tidied". For example, to convert value x in the untidied unit |
| // to the tidied unit, multiply x by factor. |
| func tidyUnit(unit string) (tidied string, factor float64) { |
| // Fast path for units from testing package. |
| switch unit { |
| case "ns/op": |
| return "sec/op", 1e-9 |
| case "MB/s": |
| return "B/s", 1e6 |
| case "B/op", "allocs/op": |
| return unit, 1 |
| } |
| // Fast path for units with no normalization. |
| if !(strings.Contains(unit, "ns") || strings.Contains(unit, "MB")) { |
| return unit, 1 |
| } |
| |
| // Check the cache. |
| if tc, ok := tidyCache.Load(unit); ok { |
| tc := tc.(*tidyEntry) |
| return tc.tidied, tc.factor |
| } |
| |
| // Do the hard work and cache it. |
| tidied, factor = tidyUnitUncached(unit) |
| tidyCache.Store(unit, &tidyEntry{tidied, factor}) |
| return |
| } |
| |
| func tidyUnitUncached(unit string) (tidied string, factor float64) { |
| type edit struct { |
| pos, len int |
| replace string |
| } |
| |
| // The caller has handled the fast paths. Parse the unit. |
| factor = 1 |
| p := newParser(unit) |
| edits := make([]edit, 0, 4) |
| for p.next() { |
| if p.denom { |
| // Don't edit in the denominator. |
| continue |
| } |
| switch p.tok { |
| case "ns": |
| edits = append(edits, edit{p.pos, len("ns"), "sec"}) |
| factor /= 1e9 |
| case "MB": |
| edits = append(edits, edit{p.pos, len("MB"), "B"}) |
| factor *= 1e6 |
| } |
| } |
| // Apply edits. |
| for i := len(edits) - 1; i >= 0; i-- { |
| e := edits[i] |
| unit = unit[:e.pos] + e.replace + unit[e.pos+e.len:] |
| } |
| return unit, factor |
| } |