Skip to content

Instantly share code, notes, and snippets.

@alireza-ahmadi
Last active August 3, 2021 03:58
Show Gist options
  • Save alireza-ahmadi/36126196f4357596afa8078a0b50b679 to your computer and use it in GitHub Desktop.
Save alireza-ahmadi/36126196f4357596afa8078a0b50b679 to your computer and use it in GitHub Desktop.
A quick benchmark of two slightly different solutions for checking whether an interface has any empty fields or not
# ----------------- 1 ----------------
# Make sure that both methods work as expected
go test
# Output ->
# PASS
# ok github.com/alireza-ahmadi/isempty 0.677s
# ----------------- 2 ----------------
# Run benchmark to understand the performance difference
go test -bench=.
# Output ->
# goos: darwin
# goarch: amd64
# pkg: github.com/alireza-ahmadi/isempty
# BenchmarkIsEmpty-4 523806 2128 ns/op
# BenchmarkIsEmptyOptimized-4 3259857 474 ns/op
# PASS
# ok github.com/alireza-ahmadi/isempty 4.580s
package isempty
import "reflect"
// IsEmpty checks given interface recursively to determine whether it has an empty field or not
func IsEmpty(v interface{}) bool {
if v == nil {
return true
}
value := reflect.ValueOf(v)
switch value.Kind() {
case reflect.Ptr:
deref := value.Elem().Interface()
return IsEmpty(deref)
case reflect.Struct:
for i := 0; i < value.NumField(); i++ {
if IsEmpty(value.Field(i).Interface()) {
return true
}
}
case reflect.Slice:
for i := 0; i < value.Len(); i++ {
if IsEmpty(value.Index(i).Interface()) {
return true
}
}
case reflect.Map:
for _, key := range value.MapKeys() {
if IsEmpty(value.MapIndex(key).Interface()) {
return true
}
}
default:
zero := reflect.Zero(value.Type())
return reflect.DeepEqual(v, zero.Interface())
}
return false
}
// IsEmptyOptimized checks given interface recursively to determine whether it has an empty field or not
func IsEmptyOptimized(v interface{}) bool {
if v == nil {
return true
}
value := reflect.ValueOf(v)
return isValueEmpty(value)
}
func isValueEmpty(value reflect.Value) bool {
switch value.Kind() {
case reflect.Ptr:
deref := value.Elem()
return isValueEmpty(deref)
case reflect.Struct:
for i := 0; i < value.NumField(); i++ {
if isValueEmpty(value.Field(i)) {
return true
}
}
case reflect.Slice:
for i := 0; i < value.Len(); i++ {
if isValueEmpty(value.Index(i)) {
return true
}
}
case reflect.Map:
for _, key := range value.MapKeys() {
if isValueEmpty(value.MapIndex(key)) {
return true
}
}
default:
return value.IsZero()
}
return false
}
package isempty_test
import (
"testing"
"github.com/alireza-ahmadi/isempty"
)
type Level1 struct {
Field1 string
Field2 string
Field3 float32
Field4 float32
Field5 Level2
}
type Level2 struct {
Field1 int
Field2 int
Field3 int
Field4 int
Field5 Level3
}
type Level3 struct {
Field1 string
Field2 string
Field3 string
Field4 string
Field5 Level4
}
type Level4 struct {
Field1 int
}
func makeSamples() (Level1, Level1) {
// There are many missing scenarios that should be tested for a production-grade
// code but ATM I don't have the time to define all of the scenarios.
withEmptyField := Level1{
Field1: "a",
Field2: "b",
Field3: 2.25,
Field4: 2.25,
Field5: Level2{
Field1: 1,
Field2: 2,
Field3: 3,
Field4: 4,
Field5: Level3{
Field1: "a",
Field2: "b",
Field3: "c",
Field4: "d",
// Missing Field5
},
},
}
withoutEmptyField := Level1{
Field1: "a",
Field2: "b",
Field3: 2.25,
Field4: 2.25,
Field5: Level2{
Field1: 1,
Field2: 2,
Field3: 3,
Field4: 4,
Field5: Level3{
Field1: "a",
Field2: "b",
Field3: "c",
Field4: "d",
Field5: Level4{
Field1: 1,
},
},
},
}
return withEmptyField, withoutEmptyField
}
func TestIsEmpty(t *testing.T) {
withEmptyField, withoutEmptyField := makeSamples()
if !isempty.IsEmpty(withEmptyField) {
t.Fatal("Should return true when object has an emtpy field")
}
if isempty.IsEmpty(withoutEmptyField) {
t.Fatal("Should return false when object has no emtpy fields")
}
}
func TestIsEmptyOptimized(t *testing.T) {
withEmptyField, withoutEmptyField := makeSamples()
if !isempty.IsEmptyOptimized(withEmptyField) {
t.Fatal("Should return true when object has an emtpy field")
}
if isempty.IsEmptyOptimized(withoutEmptyField) {
t.Fatal("Should return false when object has no emtpy fields")
}
}
func BenchmarkIsEmpty(b *testing.B) {
withEmptyField, _ := makeSamples()
b.ResetTimer()
for i := 0; i < b.N; i++ {
isempty.IsEmpty(withEmptyField)
}
}
func BenchmarkIsEmptyOptimized(b *testing.B) {
withEmptyField, _ := makeSamples()
b.ResetTimer()
for i := 0; i < b.N; i++ {
isempty.IsEmptyOptimized(withEmptyField)
}
}
@alireza-ahmadi
Copy link
Author

Thank you.

@rezakamalifard anytime!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment