diff --git a/src/engine/engine_util_misc.c b/src/engine/engine_util_misc.c index 9bc2a19a..010b25c0 100644 --- a/src/engine/engine_util_misc.c +++ b/src/engine/engine_util_misc.c @@ -1332,26 +1332,8 @@ int mju_isZero(const mjtNum* vec, int n) { // return 1 if all elements are 0 int mju_isZeroByte(const unsigned char* vec, int n) { - size_t i = 0; - - // unroll using 8-byte chunks - const uint64_t* vec64 = (const uint64_t*)vec; - size_t n64 = n / sizeof(uint64_t); - for (; i < n64; ++i) { - if (vec64[i]) { - return 0; - } - } - - // remaining bytes - i *= sizeof(uint64_t); - for (; i < n; ++i) { - if (vec[i]) { - return 0; - } - } - - return 1; + if (!n || *vec) return !n; + return memcmp(vec, vec + 1, n - 1) == 0; } diff --git a/src/engine/engine_util_misc.h b/src/engine/engine_util_misc.h index 3c2fd1ba..b682c4d0 100644 --- a/src/engine/engine_util_misc.h +++ b/src/engine/engine_util_misc.h @@ -136,7 +136,7 @@ MJAPI int mju_isBad(mjtNum x); // return 1 if all elements are numerically 0 (-0.0 treated as zero) MJAPI int mju_isZero(const mjtNum* vec, int n); -// return 1 if all elements are 0x00, can be ~2x faster than mju_isZero +// return 1 if all elements are 0x00, faster than mju_isZero MJAPI int mju_isZeroByte(const unsigned char* vec, int n); // set integer vector to 0 diff --git a/test/benchmark/iszero_benchmark_test.cc b/test/benchmark/iszero_benchmark_test.cc index 7cc283b3..ed2886ac 100644 --- a/test/benchmark/iszero_benchmark_test.cc +++ b/test/benchmark/iszero_benchmark_test.cc @@ -46,6 +46,11 @@ ABSL_ATTRIBUTE_NO_TAIL_CALL static void IzZeroBenchmark( } // Define benchmarks. +void BM_isZeroByte_10(benchmark::State& state) { + IzZeroBenchmark(state, 10, true); +} +BENCHMARK(BM_isZeroByte_10); + void BM_isZeroByte_1e2(benchmark::State& state) { IzZeroBenchmark(state, 100, true); } @@ -56,6 +61,11 @@ void BM_isZeroByte_1e5(benchmark::State& state) { } BENCHMARK(BM_isZeroByte_1e5); +void BM_isZero_10(benchmark::State& state) { + IzZeroBenchmark(state, 10, false); +} +BENCHMARK(BM_isZero_10); + void BM_isZero_1e2(benchmark::State& state) { IzZeroBenchmark(state, 100, false); } diff --git a/test/engine/engine_util_misc_test.cc b/test/engine/engine_util_misc_test.cc index f26fae3f..80e9a495 100644 --- a/test/engine/engine_util_misc_test.cc +++ b/test/engine/engine_util_misc_test.cc @@ -360,6 +360,35 @@ TEST_F(UtilMiscTest, MjuIsZero) { EXPECT_EQ(mju_isZeroByte((const unsigned char*)vec, sizeof(mjtNum)), 0); } +TEST_F(UtilMiscTest, MjuIsZeroByte) { + // Zero length array + EXPECT_TRUE(mju_isZeroByte(nullptr, 0)); + + // zero length array with non-null pointer + unsigned char vec0[1] = {0}; + EXPECT_TRUE(mju_isZeroByte(vec0, sizeof(vec0))); + + // one zero element array + unsigned char vec1[1] = {0}; + EXPECT_TRUE(mju_isZeroByte(vec1, sizeof(vec1))); + + // one non-zero element array + unsigned char vec2[2] = {1}; + EXPECT_FALSE(mju_isZeroByte(vec2, sizeof(vec2))); + + // Non-zero at start + unsigned char vec3[3] = {1, 0, 0}; + EXPECT_FALSE(mju_isZeroByte(vec3, sizeof(vec3))); + + // Non-zero at end + unsigned char vec4[3] = {0, 0, 1}; + EXPECT_FALSE(mju_isZeroByte(vec4, sizeof(vec4))); + + // Non-zero in middle + unsigned char vec5[3] = {0, 1, 0}; + EXPECT_FALSE(mju_isZeroByte(vec5, sizeof(vec5))); +} + // --------------------------------- Interpolation ----------------------------- using InterpolationTest = MujocoTest;