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  }