Skip to content

Commit

Permalink
host: convert multiple benchmark frequency calculation to use physic (g…
Browse files Browse the repository at this point in the history
…oogle#389)

This changes the calculation to be purely integer based, with clear
rounding rules. Include a unit test to confirm the actual range.
  • Loading branch information
maruel authored Jan 22, 2019
1 parent 6bc9256 commit c10cefa
Show file tree
Hide file tree
Showing 6 changed files with 396 additions and 36 deletions.
42 changes: 30 additions & 12 deletions host/allwinner/allwinnersmoketest/benchmark_gpio_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/physic"
)

// runGPIOBenchmark runs the standardized GPIO benchmark for this specific
Expand Down Expand Up @@ -367,19 +368,36 @@ func printBench(name string, r testing.BenchmarkResult) {
fmt.Fprintf(os.Stderr, "unexpected %d bytes allocated as %d calls\n", r.MemBytes, r.MemAllocs)
return
}
fmt.Printf("%s \t%s\t%s\n", name, r, toHz(r.N, r.T))
fmt.Printf("%s \t%s\t%s\n", name, r, toHz(&r))
}

func toHz(n int, t time.Duration) string {
// Periph has a ban on float64 on the library but it's not too bad on unit
// and smoke tests.
hz := float64(n) * float64(time.Second) / float64(t)
switch {
case hz >= 1000000:
return fmt.Sprintf("%.1fMHz", hz*0.000001)
case hz >= 1000:
return fmt.Sprintf("%.1fkHz", hz*0.001)
default:
return fmt.Sprintf("%.1fHz", hz)
// toHz converts a benchmark result to a frequency keeping the most precision
// as possible.
//
// Time is used at 1µs resolution, and lowered at 1ms resolution if the
// duration is over 10s.
func toHz(r *testing.BenchmarkResult) physic.Frequency {
if r.T <= 0 {
return 0
}
n := int64(r.N)
t := r.T.Nanoseconds()

timeRes := time.Microsecond
if r.T > 10*time.Second {
// Reduce the resolution to millisecond. This is needed to not overflow
// int64.
timeRes = time.Millisecond
}

// Leverage the fact that the number of occurences is generally a large
// base10. Still, make sure to keep at least 6 digits of resolution.
factor := int64(1)
for (n%10) == 0 && n > 1000000 {
n /= 10
factor *= 10
}
n *= int64(physic.Hertz) * int64(time.Second/timeRes)
t /= int64(timeRes)
return physic.Frequency(((n + (t / 2)) / t) * factor)
}
102 changes: 102 additions & 0 deletions host/allwinner/allwinnersmoketest/benchmark_gpio_support_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2019 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package allwinnersmoketest

import (
"testing"
"time"

"periph.io/x/periph/conn/physic"
)

func TestToHz(t *testing.T) {
data := []struct {
N int
T time.Duration
expected physic.Frequency
}{
{
0,
time.Second,
0,
},
{
1,
0,
0,
},
{
1,
time.Millisecond,
physic.KiloHertz,
},
{
1,
time.Second,
physic.Hertz,
},
{
3,
7 * time.Millisecond,
// 3/7 with perfect rounding.
428571429 * physic.MicroHertz,
},
{
3000,
7 * time.Microsecond,
// 3/7 with perfect rounding.
428571428571429 * physic.MicroHertz,
},
{
1000000,
1000 * time.Second,
physic.KiloHertz,
},
{
1000000,
time.Second,
physic.MegaHertz,
},
{
1000000,
time.Millisecond,
physic.GigaHertz,
},
{
1000000000,
1000 * time.Second,
physic.MegaHertz,
},
{
1000000000,
time.Second,
physic.GigaHertz,
},
{
1234556000,
// 2.3s.
2345567891 * time.Nanosecond,
// 10 digits of resolution for 526.336MHz.
526335849711 * physic.MilliHertz,
},
{
1000000000,
time.Millisecond,
physic.TeraHertz,
},
{
300000000,
7 * time.Millisecond,
// 3/7 with pretty good rounding, keeping in mind that's 42.857GHz.
42857142857143 * physic.MilliHertz,
},
}
for i, line := range data {
r := testing.BenchmarkResult{N: line.N, T: line.T}
if actual := toHz(&r); actual != line.expected {
t.Fatalf("#%d: toHz(%d, %s) = %s(%d); expected %s(%d)", i, line.N, line.T, actual, actual, line.expected, line.expected)
}
}
}
42 changes: 30 additions & 12 deletions host/bcm283x/bcm283xsmoketest/benchmark_gpio_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/physic"
)

// runGPIOBenchmark runs the standardized GPIO benchmark for this specific
Expand Down Expand Up @@ -367,19 +368,36 @@ func printBench(name string, r testing.BenchmarkResult) {
fmt.Fprintf(os.Stderr, "unexpected %d bytes allocated as %d calls\n", r.MemBytes, r.MemAllocs)
return
}
fmt.Printf("%s \t%s\t%s\n", name, r, toHz(r.N, r.T))
fmt.Printf("%s \t%s\t%s\n", name, r, toHz(&r))
}

func toHz(n int, t time.Duration) string {
// Periph has a ban on float64 on the library but it's not too bad on unit
// and smoke tests.
hz := float64(n) * float64(time.Second) / float64(t)
switch {
case hz >= 1000000:
return fmt.Sprintf("%.1fMHz", hz*0.000001)
case hz >= 1000:
return fmt.Sprintf("%.1fkHz", hz*0.001)
default:
return fmt.Sprintf("%.1fHz", hz)
// toHz converts a benchmark result to a frequency keeping the most precision
// as possible.
//
// Time is used at 1µs resolution, and lowered at 1ms resolution if the
// duration is over 10s.
func toHz(r *testing.BenchmarkResult) physic.Frequency {
if r.T <= 0 {
return 0
}
n := int64(r.N)
t := r.T.Nanoseconds()

timeRes := time.Microsecond
if r.T > 10*time.Second {
// Reduce the resolution to millisecond. This is needed to not overflow
// int64.
timeRes = time.Millisecond
}

// Leverage the fact that the number of occurences is generally a large
// base10. Still, make sure to keep at least 6 digits of resolution.
factor := int64(1)
for (n%10) == 0 && n > 1000000 {
n /= 10
factor *= 10
}
n *= int64(physic.Hertz) * int64(time.Second/timeRes)
t /= int64(timeRes)
return physic.Frequency(((n + (t / 2)) / t) * factor)
}
102 changes: 102 additions & 0 deletions host/bcm283x/bcm283xsmoketest/benchmark_gpio_support_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright 2019 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package bcm283xsmoketest

import (
"testing"
"time"

"periph.io/x/periph/conn/physic"
)

func TestToHz(t *testing.T) {
data := []struct {
N int
T time.Duration
expected physic.Frequency
}{
{
0,
time.Second,
0,
},
{
1,
0,
0,
},
{
1,
time.Millisecond,
physic.KiloHertz,
},
{
1,
time.Second,
physic.Hertz,
},
{
3,
7 * time.Millisecond,
// 3/7 with perfect rounding.
428571429 * physic.MicroHertz,
},
{
3000,
7 * time.Microsecond,
// 3/7 with perfect rounding.
428571428571429 * physic.MicroHertz,
},
{
1000000,
1000 * time.Second,
physic.KiloHertz,
},
{
1000000,
time.Second,
physic.MegaHertz,
},
{
1000000,
time.Millisecond,
physic.GigaHertz,
},
{
1000000000,
1000 * time.Second,
physic.MegaHertz,
},
{
1000000000,
time.Second,
physic.GigaHertz,
},
{
1234556000,
// 2.3s.
2345567891 * time.Nanosecond,
// 10 digits of resolution for 526.336MHz.
526335849711 * physic.MilliHertz,
},
{
1000000000,
time.Millisecond,
physic.TeraHertz,
},
{
300000000,
7 * time.Millisecond,
// 3/7 with pretty good rounding, keeping in mind that's 42.857GHz.
42857142857143 * physic.MilliHertz,
},
}
for i, line := range data {
r := testing.BenchmarkResult{N: line.N, T: line.T}
if actual := toHz(&r); actual != line.expected {
t.Fatalf("#%d: toHz(%d, %s) = %s(%d); expected %s(%d)", i, line.N, line.T, actual, actual, line.expected, line.expected)
}
}
}
42 changes: 30 additions & 12 deletions host/sysfs/sysfssmoketest/benchmark_gpio_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"time"

"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/physic"
)

// runGPIOBenchmark runs the standardized GPIO benchmark for this specific
Expand Down Expand Up @@ -367,19 +368,36 @@ func printBench(name string, r testing.BenchmarkResult) {
fmt.Fprintf(os.Stderr, "unexpected %d bytes allocated as %d calls\n", r.MemBytes, r.MemAllocs)
return
}
fmt.Printf("%s \t%s\t%s\n", name, r, toHz(r.N, r.T))
fmt.Printf("%s \t%s\t%s\n", name, r, toHz(&r))
}

func toHz(n int, t time.Duration) string {
// Periph has a ban on float64 on the library but it's not too bad on unit
// and smoke tests.
hz := float64(n) * float64(time.Second) / float64(t)
switch {
case hz >= 1000000:
return fmt.Sprintf("%.1fMHz", hz*0.000001)
case hz >= 1000:
return fmt.Sprintf("%.1fkHz", hz*0.001)
default:
return fmt.Sprintf("%.1fHz", hz)
// toHz converts a benchmark result to a frequency keeping the most precision
// as possible.
//
// Time is used at 1µs resolution, and lowered at 1ms resolution if the
// duration is over 10s.
func toHz(r *testing.BenchmarkResult) physic.Frequency {
if r.T <= 0 {
return 0
}
n := int64(r.N)
t := r.T.Nanoseconds()

timeRes := time.Microsecond
if r.T > 10*time.Second {
// Reduce the resolution to millisecond. This is needed to not overflow
// int64.
timeRes = time.Millisecond
}

// Leverage the fact that the number of occurences is generally a large
// base10. Still, make sure to keep at least 6 digits of resolution.
factor := int64(1)
for (n%10) == 0 && n > 1000000 {
n /= 10
factor *= 10
}
n *= int64(physic.Hertz) * int64(time.Second/timeRes)
t /= int64(timeRes)
return physic.Frequency(((n + (t / 2)) / t) * factor)
}
Loading

0 comments on commit c10cefa

Please sign in to comment.