github.com/jgbaldwinbrown/perf@v0.1.1/benchunit/tidy.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package benchunit 6 7 import ( 8 "strings" 9 "sync" 10 ) 11 12 type tidyEntry struct { 13 tidied string 14 factor float64 15 } 16 17 var tidyCache sync.Map // unit string -> *tidyCache 18 19 // Tidy normalizes a value with a (possibly pre-scaled) unit into base units. 20 // For example, if unit is "ns" or "MB", it will re-scale the value to 21 // "sec" or "B" units, respectively. It returns the re-scaled value and 22 // its new unit. If the value is already in base units, it does nothing. 23 func Tidy(value float64, unit string) (tidiedValue float64, tidiedUnit string) { 24 newUnit, factor := tidyUnit(unit) 25 return value * factor, newUnit 26 } 27 28 // tidyUnit normalizes common pre-scaled units like "ns" to "sec" and 29 // "MB" to "B". It returns the tidied version of unit and the 30 // multiplicative factor to convert a value in unit "unit" to a value in 31 // unit "tidied". For example, to convert value x in the untidied unit 32 // to the tidied unit, multiply x by factor. 33 func tidyUnit(unit string) (tidied string, factor float64) { 34 // Fast path for units from testing package. 35 switch unit { 36 case "ns/op": 37 return "sec/op", 1e-9 38 case "MB/s": 39 return "B/s", 1e6 40 case "B/op", "allocs/op": 41 return unit, 1 42 } 43 // Fast path for units with no normalization. 44 if !(strings.Contains(unit, "ns") || strings.Contains(unit, "MB")) { 45 return unit, 1 46 } 47 48 // Check the cache. 49 if tc, ok := tidyCache.Load(unit); ok { 50 tc := tc.(*tidyEntry) 51 return tc.tidied, tc.factor 52 } 53 54 // Do the hard work and cache it. 55 tidied, factor = tidyUnitUncached(unit) 56 tidyCache.Store(unit, &tidyEntry{tidied, factor}) 57 return 58 } 59 60 func tidyUnitUncached(unit string) (tidied string, factor float64) { 61 type edit struct { 62 pos, len int 63 replace string 64 } 65 66 // The caller has handled the fast paths. Parse the unit. 67 factor = 1 68 p := newParser(unit) 69 edits := make([]edit, 0, 4) 70 for p.next() { 71 if p.denom { 72 // Don't edit in the denominator. 73 continue 74 } 75 switch p.tok { 76 case "ns": 77 edits = append(edits, edit{p.pos, len("ns"), "sec"}) 78 factor /= 1e9 79 case "MB": 80 edits = append(edits, edit{p.pos, len("MB"), "B"}) 81 factor *= 1e6 82 } 83 } 84 // Apply edits. 85 for i := len(edits) - 1; i >= 0; i-- { 86 e := edits[i] 87 unit = unit[:e.pos] + e.replace + unit[e.pos+e.len:] 88 } 89 return unit, factor 90 }