为何C语言实现的欧几里得距离函数运行速度慢于Java?排查与优化实践
问题背景
我分别用C和Java实现了3D点的欧几里得距离函数并做基准测试,最初发现C版本运行时间约1.688852秒,而Java版本仅需0.355038秒。即使尝试移除sqrt、手动内联代码、修改函数签名为直接接收6个double坐标(避免指针访问),C版本的耗时也没明显改善。
编译和运行环境:
- C:用
cc -O2 main.c -lm编译 - Java:IntelliJ IDEA默认JVM选项(Java 8,OpenJDK)
初始测试代码
C版本
#include <math.h> #include <stdio.h> #include <time.h> typedef struct point3d { double x; double y; double z; } point3d_t; double distance(point3d_t *from, point3d_t *to); int main(int argc, char const *argv[]) { point3d_t from = {.x = 2.3, .y = 3.45, .z = 4.56}; point3d_t to = {.x = 5.678, .y = 3.45, .z = -9.0781}; double time = 0.0; int count = 10000000; for (size_t i = 0; i < count; i++) { clock_t tic = clock(); double d = distance(&from, &to); clock_t toc = clock(); time += ((double) (toc - tic) / CLOCKS_PER_SEC); } printf("Elapsed: %f seconds\n", time); return 0; } double distance(point3d_t *from, point3d_t *to) { double dx = to->x - from->x; double dy = to->y - from->y; double dz = to->z - from->z; double d2 = (dx * dx) + (dy * dy) + (dz + dz); // 此处存在bug:dz+dz应为dz*dz return sqrt(d2); }
Java版本
public class App { static Random rnd = new Random(); public static void main( String[] args ) { var sw = new StopWatch(); var time = 0.0; var count = 10000000; for (int i = 0; i < count; i++) { var from = Vector3D.of(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble()); var to = Vector3D.of(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble()); sw.start(); var dist = distance(from, to); sw.stop(); time += sw.getTime(TimeUnit.NANOSECONDS); sw.reset(); } System.out.printf("Time: %f seconds\n", time / 1e09); } public static double distance(Vector3D from, Vector3D to) { var dx = to.getX() - from.getX(); var dy = to.getY() - from.getY(); var dz = to.getZ() - from.getZ(); return Math.sqrt((dx * dx) + (dy * dy) + (dz * dz)); } }
注:Java中使用随机值是为了避免JVM执行结果缓存、跳过计算之类的优化操作。
第一次优化:修正基准测试方法
最初的基准测试存在明显缺陷:C版本在循环内对单次函数调用单独计时,引入了大量额外开销;且Java和C的测试场景不一致(C用固定点,Java用随机点)。于是更新代码统一测试逻辑:
更新后的C代码
#include <math.h> #include <stdio.h> #include <time.h> typedef struct point3d { double x; double y; double z; } point3d_t; double distance(point3d_t *from, point3d_t *to); int main(int argc, char const *argv[]) { point3d_t from = {.x = 2.3, .y = 3.45, .z = 4.56}; point3d_t to = {.x = 5.678, .y = 3.45, .z = -9.0781}; struct timespec fs; struct timespec ts; long time = 0; int count = 10000000; double dist = 0; clock_gettime(CLOCK_REALTIME, &fs); for (size_t i = 0; i < count; i++) { dist = distance(&from, &to); } clock_gettime(CLOCK_REALTIME, &ts); time = ts.tv_nsec - fs.tv_nsec; if (dist == 0.001) { printf("hello\n"); } printf("Elapsed: %f sec\n", (double) time / 1e9); return 0; } double distance(point3d_t *from, point3d_t *to) { double dx = to->x - from->x; double dy = to->y - from->y; double dz = to->z - from->z; double d2 = (dx * dx) + (dy * dy) + (dz + dz); // 依然保留dz+dz的bug return sqrt(d2); }
更新后的Java代码
public class App { static Random rnd = new Random(); public static void main( String[] args ) { var from = Vector3D.of(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble()); var to = Vector3D.of(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble()); var time = 0.0; var count = 10000000; double dist = 0.0; var start = System.nanoTime(); for (int i = 0; i < count; i++) { dist = distance(from, to); } var end = System.nanoTime(); time = end - start; if (dist == rnd.nextDouble()) { System.out.printf("hello! %f\n", dist); } dist = dist + 1; System.out.printf("Time: %f sec\n", (double) time / 1e9); System.out.printf("Yohoo! %f\n", dist); } public static double distance(Vector3D from, Vector3D to) { var dx= to.getX() - from.getX(); var dy = to.getY() - from.getY(); var dz = to.getZ() - from.getZ(); return Math.sqrt((dx * dx) + (dy * dy) + (dz * dz)); } }
此时用gcc -Wall -std=gnu99 -O2 main.c -lm编译C代码,测试结果:
- C耗时:0.06323秒
- Java耗时:0.006325秒
C版本依然慢于Java,问题根源尚未找到。
最终解决:修复代码bug + 优化编译选项
经过排查,发现两个核心问题:
- 代码逻辑bug:C版本的
distance函数中,计算平方和时错误地写成了dz + dz,正确应为dz * dz。这个错误会导致d2可能为负数,调用sqrt时触发错误处理逻辑,大幅拖慢执行速度。 - 编译选项限制:GCC默认会为数学函数保留
errno错误检查,即使结果不可能出错,也会产生额外开销。添加-fno-math-errno选项可以关闭该检查,让sqrt调用更高效。
最终可用代码
C版本
#include <math.h> #include <stdio.h> #include <time.h> typedef struct point3d { double x; double y; double z; } point3d_t; double distance(point3d_t *from, point3d_t *to); int main(int argc, char const *argv[]) { point3d_t from = {.x = 2.3, .y = 3.45, .z = 4.56}; point3d_t to = {.x = 5.678, .y = 3.45, .z = -9.0781}; struct timespec fs; struct timespec ts; long time = 0; int count = 10000000; double dist = 0; clock_gettime(CLOCK_REALTIME, &fs); for (size_t i = 0; i < count; i++) { dist = distance(&from, &to); } clock_gettime(CLOCK_REALTIME, &ts); time = ts.tv_nsec - fs.tv_nsec; printf("hello %f \n", dist); printf("Elapsed: %f ns\n", (double) time); printf("Elapsed: %f sec\n", (double) time / 1e9); return 0; } double distance(point3d_t *from, point3d_t *to) { double dx = (to->x) - (from->x); double dy = (to->y) - (from->y); double dz = (to->z) - (from->z); double d2 = (dx * dx) + (dy * dy) + (dz * dz); // 修复dz*dz的bug return sqrt(d2); }
编译命令:gcc -O2 -std=gnu99 -fno-math-errno main.c -lm
Java版本
public class App { static Random rnd = new Random(); public static void main( String[] args ) { var from = Vector3D.of(2.3, 3.45, 4.56); var to = Vector3D.of(5.678, 3.45, -9.0781); var time = 0.0; var count = 10000000; double dist = 0.0; var start = System.nanoTime(); for (int i = 0; i < count; i++) { dist = distance(from, to); } var end = System.nanoTime(); time = end - start; System.out.printf("Yohoo! %f\n", dist); System.out.printf("Time: %f ns\n", (double) time / 1e9); } public static double distance(Vector3D from, Vector3D to) { var dx = to.getX() - from.getX(); var dy = to.getY() - from.getY(); var dz = to.getZ() - from.getZ(); var d2 = (dx * dx) + (dy * dy) + (dz * dz); return Math.sqrt(d2); } }
最终测试结果
- C版本耗时:几乎为0秒(271纳秒)
- Java版本耗时:0.007217秒
现在C版本的速度完全超过了Java,性能表现恢复正常!
内容的提问来源于stack exchange,提问作者kovac
相关产品推荐
相关产品推荐

