STT-tensorflow/tensorflow/go/tensor_test.go
Phil Pearl cc1273649c PR : trying again: Big performance gains for Go NewTensor and Value
Imported from GitHub PR https://github.com/tensorflow/tensorflow/pull/39117

After some helpful comments in https://github.com/tensorflow/tensorflow/pull/36578#issuecomment-622308355 I'm trying again with this performance improvement.

Please please please if it gets rolled back again because of "internal test" failures, please get some kind of debug information. Stack traces or something. Any kind of clue.

```
name                                  old time/op    new time/op    delta
Tensor/New/[150528]int32-16             1.78ms ± 4%    0.13ms ±10%   -92.63%  (p=0.000 n=8+7)
Tensor/New/[100][100][100]int32-16      13.1ms ± 1%     0.9ms ±53%   -92.81%  (p=0.000 n=8+8)
Tensor/New/[]float32-16                 3.72ms ± 1%    0.97ms ±30%   -74.04%  (p=0.000 n=8+8)
Tensor/New/[][]float32-16               4.83ms ± 2%    1.32ms ± 8%   -72.69%  (p=0.000 n=8+8)
Tensor/New/[][][]float32-16             4.81ms ± 1%    1.32ms ± 4%   -72.51%  (p=0.001 n=8+6)
Tensor/New/[]string-16                   466ms ± 1%      34ms ± 4%   -92.60%  (p=0.001 n=7+7)
Tensor/New/[][]string-16                 460ms ± 1%      35ms ± 1%   -92.45%  (p=0.000 n=8+8)
Tensor/New/[][][]string-16               462ms ± 2%      36ms ± 5%   -92.14%  (p=0.000 n=8+8)
Tensor/Value/[150528]int32-16            647µs ± 3%      82µs ± 1%   -87.28%  (p=0.000 n=8+8)
Tensor/Value/[100][100][100]int32-16    6.43ms ± 1%    0.99ms ± 3%   -84.63%  (p=0.000 n=8+8)
Tensor/Value/[]float32-16               5.57ms ± 3%    1.04ms ± 7%   -81.26%  (p=0.000 n=8+8)
Tensor/Value/[][]float32-16             6.84ms ± 1%    1.51ms ± 1%   -77.96%  (p=0.000 n=8+8)
Tensor/Value/[][][]float32-16           6.87ms ± 1%    1.52ms ± 3%   -77.80%  (p=0.001 n=7+7)
Tensor/Value/[]string-16                 268ms ± 3%      20ms ± 2%   -92.45%  (p=0.000 n=8+8)
Tensor/Value/[][]string-16               269ms ± 2%      20ms ± 1%   -92.46%  (p=0.000 n=8+7)
Tensor/Value/[][][]string-16             271ms ± 2%      20ms ± 1%   -92.55%  (p=0.000 n=8+8)

name                                  old alloc/op   new alloc/op   delta
Tensor/New/[150528]int32-16              606kB ± 0%       0kB ± 0%   -99.99%  (p=0.000 n=8+8)
Tensor/New/[100][100][100]int32-16      4.16MB ± 0%    0.00MB ± 0%  -100.00%  (p=0.000 n=7+8)
Tensor/New/[]float32-16                 4.01MB ± 0%    0.00MB ± 0%  -100.00%  (p=0.002 n=7+8)
Tensor/New/[][]float32-16               4.48MB ± 0%    0.00MB ± 0%  -100.00%  (p=0.000 n=8+8)
Tensor/New/[][][]float32-16             4.48MB ± 0%    0.00MB ± 0%  -100.00%  (p=0.002 n=7+8)
Tensor/New/[]string-16                  48.0MB ± 0%     0.0MB ± 0%  -100.00%  (p=0.000 n=7+8)
Tensor/New/[][]string-16                48.3MB ± 0%     0.0MB ± 0%  -100.00%  (p=0.000 n=7+8)
Tensor/New/[][][]string-16              48.3MB ± 0%     0.0MB ± 0%  -100.00%  (p=0.000 n=7+8)
Tensor/Value/[150528]int32-16           1.21MB ± 0%    0.61MB ± 0%   -50.00%  (p=0.000 n=8+8)
Tensor/Value/[100][100][100]int32-16    9.23MB ± 0%    4.25MB ± 0%   -53.93%  (p=0.000 n=8+8)
Tensor/Value/[]float32-16               8.01MB ± 0%    4.01MB ± 0%   -50.00%  (p=0.000 n=8+7)
Tensor/Value/[][]float32-16             9.21MB ± 0%    4.25MB ± 0%   -53.82%  (p=0.000 n=8+8)
Tensor/Value/[][][]float32-16           9.23MB ± 0%    4.25MB ± 0%   -53.93%  (p=0.000 n=8+8)
Tensor/Value/[]string-16                56.0MB ± 0%    23.0MB ± 0%   -58.91%  (p=0.000 n=8+7)
Tensor/Value/[][]string-16              58.5MB ± 0%    23.3MB ± 0%   -60.23%  (p=0.000 n=8+8)
Tensor/Value/[][][]string-16            58.5MB ± 0%    23.3MB ± 0%   -60.25%  (p=0.001 n=7+7)

name                                  old allocs/op  new allocs/op  delta
Tensor/New/[150528]int32-16               4.00 ± 0%      2.00 ± 0%   -50.00%  (p=0.000 n=8+8)
Tensor/New/[100][100][100]int32-16       10.0k ± 0%      0.0k ± 0%   -99.96%  (p=0.000 n=8+8)
Tensor/New/[]float32-16                   4.00 ± 0%      2.00 ± 0%   -50.00%  (p=0.000 n=8+8)
Tensor/New/[][]float32-16                20.0k ± 0%      0.0k ± 0%   -99.99%  (p=0.000 n=8+8)
Tensor/New/[][][]float32-16              20.0k ± 0%      0.0k ± 0%   -99.98%  (p=0.000 n=8+8)
Tensor/New/[]string-16                   4.00M ± 0%     0.00M ± 0%  -100.00%  (p=0.000 n=8+8)
Tensor/New/[][]string-16                 4.01M ± 0%     0.00M ± 0%  -100.00%  (p=0.000 n=8+8)
Tensor/New/[][][]string-16               4.01M ± 0%     0.00M ± 0%  -100.00%  (p=0.000 n=8+8)
Tensor/Value/[150528]int32-16             7.00 ± 0%      2.00 ± 0%   -71.43%  (p=0.000 n=8+8)
Tensor/Value/[100][100][100]int32-16     40.2k ± 0%      0.0k ± 0%   -99.99%  (p=0.000 n=8+8)
Tensor/Value/[]float32-16                 7.00 ± 0%      2.00 ± 0%   -71.43%  (p=0.000 n=8+8)
Tensor/Value/[][]float32-16              40.0k ± 0%      0.0k ± 0%   -99.99%  (p=0.000 n=8+8)
Tensor/Value/[][][]float32-16            40.2k ± 0%      0.0k ± 0%   -99.99%  (p=0.000 n=8+8)
Tensor/Value/[]string-16                 5.00M ± 0%     0.00M ± 0%  -100.00%  (p=0.000 n=8+8)
Tensor/Value/[][]string-16               5.02M ± 0%     0.00M ± 0%  -100.00%  (p=0.000 n=8+8)
Tensor/Value/[][][]string-16             5.02M ± 0%     0.00M ± 0%  -100.00%  (p=0.000 n=8+8)
```
Copybara import of the project:

--
fb7e6b1665204f78863b70e7f4998cabeef0898d by Phil Pearl <phil.pearl@ravelin.com>:

Add some more benchmarks

--
4ae853272b8792a599cdbd5c5af5422b23d90366 by Phil Pearl <phil.pearl@ravelin.com>:

Go: large performance gains for NewTensor and Value
COPYBARA_INTEGRATE_REVIEW=https://github.com/tensorflow/tensorflow/pull/39117 from philpearl:master 4ae853272b8792a599cdbd5c5af5422b23d90366
PiperOrigin-RevId: 318083887
Change-Id: Ie211646f11922c5b0971bbf64bdb4a0c6a844985
2020-06-24 10:08:51 -07:00

353 lines
8.9 KiB
Go

/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"bytes"
"fmt"
"io"
"reflect"
"testing"
)
func TestNewTensor(t *testing.T) {
var tests = []struct {
shape []int64
value interface{}
}{
{nil, bool(true)},
{nil, int8(5)},
{nil, int16(5)},
{nil, int32(5)},
{nil, int64(5)},
{nil, uint8(5)},
{nil, uint16(5)},
{nil, uint32(5)},
{nil, uint64(5)},
{nil, float32(5)},
{nil, float64(5)},
{nil, complex(float32(5), float32(6))},
{nil, complex(float64(5), float64(6))},
{nil, "a string"},
{[]int64{1}, []uint32{1}},
{[]int64{1}, []uint64{1}},
{[]int64{2}, []bool{true, false}},
{[]int64{1}, []float64{1}},
{[]int64{1}, [1]float64{1}},
{[]int64{1, 1}, [1][1]float64{{1}}},
{[]int64{1, 1, 1}, [1][1][]float64{{{1}}}},
{[]int64{1, 1, 2}, [1][][2]float64{{{1, 2}}}},
{[]int64{1, 1, 1, 1}, [1][][1][]float64{{{{1}}}}},
{[]int64{2}, []string{"string", "slice"}},
{[]int64{2}, [2]string{"string", "array"}},
{[]int64{3, 2}, [][]float64{{1, 2}, {3, 4}, {5, 6}}},
{[]int64{2, 3}, [2][3]float64{{1, 2, 3}, {3, 4, 6}}},
{[]int64{4, 3, 2}, [][][]float64{
{{1, 2}, {3, 4}, {5, 6}},
{{7, 8}, {9, 10}, {11, 12}},
{{0, -1}, {-2, -3}, {-4, -5}},
{{-6, -7}, {-8, -9}, {-10, -11}},
}},
{[]int64{2, 0}, [][]int64{{}, {}}},
{[]int64{2, 2}, [][]string{{"row0col0", "row0,col1"}, {"row1col0", "row1,col1"}}},
{[]int64{2, 3}, [2][3]string{
{"row0col0", "row0,col1", "row0,col2"},
{"row1col0", "row1,col1", "row1,col2"},
}},
}
var errorTests = []interface{}{
struct{ a int }{5},
new(int32),
new([]int32),
// native ints not supported
int(5),
[]int{5},
// Mismatched dimensions
[][]float32{{1, 2, 3}, {4}},
// Mismatched dimensions. Should return "mismatched slice lengths" error instead of "BUG"
[][][]float32{{{1, 2}, {3, 4}}, {{1}, {3}}},
// Mismatched dimensions. Should return error instead of valid tensor
[][][]float32{{{1, 2}, {3, 4}}, {{1}, {3}}, {{1, 2, 3}, {2, 3, 4}}},
// Mismatched dimensions for strings
[][]string{{"abc"}, {"abcd", "abcd"}},
}
for _, test := range tests {
tensor, err := NewTensor(test.value)
if err != nil {
t.Errorf("NewTensor(%v): %v", test.value, err)
continue
}
if !reflect.DeepEqual(test.shape, tensor.Shape()) {
t.Errorf("Tensor.Shape(): got %v, want %v", tensor.Shape(), test.shape)
}
// Test that encode and decode gives the same value. We skip arrays because
// they're returned as slices.
if reflect.TypeOf(test.value).Kind() != reflect.Array {
got := tensor.Value()
if !reflect.DeepEqual(test.value, got) {
t.Errorf("encode/decode: got %v, want %v", got, test.value)
}
}
}
for _, test := range errorTests {
tensor, err := NewTensor(test)
if err == nil {
t.Errorf("NewTensor(%v): %v", test, err)
}
if tensor != nil {
t.Errorf("NewTensor(%v) = %v, want nil", test, tensor)
}
}
}
func TestTensorSerialization(t *testing.T) {
var tests = []interface{}{
bool(true),
int8(5),
int16(5),
int32(5),
int64(5),
uint8(5),
uint16(5),
float32(5),
float64(5),
complex(float32(5), float32(6)),
complex(float64(5), float64(6)),
[]float64{1},
[][]float32{{1, 2}, {3, 4}, {5, 6}},
[][][]int8{
{{1, 2}, {3, 4}, {5, 6}},
{{7, 8}, {9, 10}, {11, 12}},
{{0, -1}, {-2, -3}, {-4, -5}},
{{-6, -7}, {-8, -9}, {-10, -11}},
},
[]bool{true, false, true},
}
for _, v := range tests {
t1, err := NewTensor(v)
if err != nil {
t.Errorf("(%v): %v", v, err)
continue
}
buf := new(bytes.Buffer)
n, err := t1.WriteContentsTo(buf)
if err != nil {
t.Errorf("(%v): %v", v, err)
continue
}
if n != int64(buf.Len()) {
t.Errorf("(%v): WriteContentsTo said it wrote %v bytes, but wrote %v", v, n, buf.Len())
}
t2, err := ReadTensor(t1.DataType(), t1.Shape(), buf)
if err != nil {
t.Errorf("(%v): %v", v, err)
continue
}
if buf.Len() != 0 {
t.Errorf("(%v): %v bytes written by WriteContentsTo not read by ReadTensor", v, buf.Len())
}
if got, want := t2.DataType(), t1.DataType(); got != want {
t.Errorf("(%v): Got %v, want %v", v, got, want)
}
if got, want := t2.Shape(), t1.Shape(); !reflect.DeepEqual(got, want) {
t.Errorf("(%v): Got %v, want %v", v, got, want)
}
if got, want := t2.Value(), v; !reflect.DeepEqual(got, want) {
t.Errorf("(%v): Got %v, want %v", v, got, want)
}
}
}
func TestReadTensorDoesNotReadBeyondContent(t *testing.T) {
t1, _ := NewTensor(int8(7))
t2, _ := NewTensor(float32(2.718))
buf := new(bytes.Buffer)
if _, err := t1.WriteContentsTo(buf); err != nil {
t.Fatal(err)
}
if _, err := t2.WriteContentsTo(buf); err != nil {
t.Fatal(err)
}
t3, err := ReadTensor(t1.DataType(), t1.Shape(), buf)
if err != nil {
t.Fatal(err)
}
t4, err := ReadTensor(t2.DataType(), t2.Shape(), buf)
if err != nil {
t.Fatal(err)
}
if v, ok := t3.Value().(int8); !ok || v != 7 {
t.Errorf("Got (%v (%T), %v), want (7 (int8), true)", v, v, ok)
}
if v, ok := t4.Value().(float32); !ok || v != 2.718 {
t.Errorf("Got (%v (%T), %v), want (2.718 (float32), true)", v, v, ok)
}
}
func TestTensorSerializationErrors(t *testing.T) {
// String tensors cannot be serialized
t1, err := NewTensor("abcd")
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
if n, err := t1.WriteContentsTo(buf); n != 0 || err == nil || buf.Len() != 0 {
t.Errorf("Got (%v, %v, %v) want (0, <non-nil>, 0)", n, err, buf.Len())
}
// Should fail to read a truncated value.
if t1, err = NewTensor(int8(8)); err != nil {
t.Fatal(err)
}
n, err := t1.WriteContentsTo(buf)
if err != nil {
t.Fatal(err)
}
r := bytes.NewReader(buf.Bytes()[:n-1])
if _, err = ReadTensor(t1.DataType(), t1.Shape(), r); err == nil {
t.Error("ReadTensor should have failed if the tensor content was truncated")
}
}
func TestReadTensorReadAll(t *testing.T) {
// Get the bytes of a tensor.
a := []float32{1.1, 1.2, 1.3}
ats, err := NewTensor(a)
if err != nil {
t.Fatal(err)
}
abuf := new(bytes.Buffer)
if _, err := ats.WriteContentsTo(abuf); err != nil {
t.Fatal(err)
}
// Get the bytes of another tensor.
b := []float32{1.1, 1.2, 1.3}
bts, err := NewTensor(b)
if err != nil {
t.Fatal(err)
}
bbuf := new(bytes.Buffer)
if _, err := bts.WriteContentsTo(bbuf); err != nil {
t.Fatal(err)
}
// Check that ReadTensor reads all bytes of both tensors, when the situation
// requires one than reads.
abbuf := io.MultiReader(abuf, bbuf)
abts, err := ReadTensor(Float, []int64{2, 3}, abbuf)
if err != nil {
t.Fatal(err)
}
abtsf32 := abts.Value().([][]float32)
expected := [][]float32{a, b}
if len(abtsf32) != 2 {
t.Fatalf("first dimension %d is not 2", len(abtsf32))
}
for i := 0; i < 2; i++ {
if len(abtsf32[i]) != 3 {
t.Fatalf("second dimension %d is not 3", len(abtsf32[i]))
}
for j := 0; j < 3; j++ {
if abtsf32[i][j] != expected[i][j] {
t.Errorf("value at %d %d not equal %f %f", i, j, abtsf32[i][j], expected[i][j])
}
}
}
}
func benchmarkNewTensor(b *testing.B, v interface{}) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if t, err := NewTensor(v); err != nil || t == nil {
b.Fatalf("(%v, %v)", t, err)
}
}
}
func benchmarkValueTensor(b *testing.B, v interface{}) {
t, err := NewTensor(v)
if err != nil {
b.Fatalf("(%v, %v)", t, err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = t.Value()
}
}
func BenchmarkTensor(b *testing.B) {
// Some sample sizes from the Inception image labeling model.
// Where input tensors correspond to a 224x224 RGB image
// flattened into a vector.
var vector [224 * 224 * 3]int32
var arrays [100][100][100]int32
l3 := make([][][]float32, 100)
l2 := make([][]float32, 100*100)
l1 := make([]float32, 100*100*100)
for i := range l2 {
l2[i] = l1[i*100 : (i+1)*100]
}
for i := range l3 {
l3[i] = l2[i*100 : (i+1)*100]
}
s1 := make([]string, 100*100*100)
s2 := make([][]string, 100*100)
s3 := make([][][]string, 100)
for i := range s1 {
s1[i] = "cheesit"
}
for i := range s2 {
s2[i] = s1[i*100 : (i+1)*100]
}
for i := range s3 {
s3[i] = s2[i*100 : (i+1)*100]
}
tests := []interface{}{
vector,
arrays,
l1,
l2,
l3,
s1,
s2,
s3,
}
b.Run("New", func(b *testing.B) {
for _, test := range tests {
b.Run(fmt.Sprintf("%T", test), func(b *testing.B) { benchmarkNewTensor(b, test) })
}
})
b.Run("Value", func(b *testing.B) {
for _, test := range tests {
b.Run(fmt.Sprintf("%T", test), func(b *testing.B) { benchmarkValueTensor(b, test) })
}
})
}