Java比C快2倍(作为C ++的一个子集)

下面的代码是一种非常低效的乘法算法。 它被写成测试目的。 我相信我用不同的语言编写了相同的代码。

正下方是运行代码的结果。

OS: Windows 7 language: C (as a subset of C++) compiler: Visual C++ optimization option: /Ox /Oi /Ot /Oy /GL running time (seconds): 40 +/- 1 compiler: MinGW/gcc optimization option: -O3 march=native running time (seconds): 81 +/- 1 compiler: MinGW/g++ optimization option: -O3 march=native running time (seconds): 82 +/- 1 language: Java compiler: Oracle JDK VM: Oracle JVM running time (seconds): 18 +/- 1 

我相信我在我的C代码中做了一些糟糕的事情,完全优化的编译器不能以任何方式优化。 如果有任何大问题请告诉我。 我正在计划一个项目,其中有一个部分处理大量的计算。 我决定用C编写这个核心计算部分,但是有了这种结果,我宁可用Java编写所有东西; 它更容易,甚至更快? 我仍然相信C,所以如果我的代码有任何问题,请告诉我。 我的期望是Java版本应该慢1.5倍或更多,但它在某种程度上优于C.

TEST.C

 #include  #include  #include  #include  typedef signed char byte; typedef struct _Array { byte *data; int len; } Array; void new_Array(Array *a, int len) { a->data = (byte *)malloc(len * sizeof(byte)); a->len = len; } void del_Array(Array *a) { free(a->data); } typedef struct _BUI { Array num; int len; } BUI[1]; void new_BUI(BUI b, const char *s) { int len = strlen(s); b->len = len; new_Array(&b->num, len); for (int i = 0; i num.data[i] = s[len - i - 1] - '0'; } } void del_BUI(BUI b) { del_Array(&b->num); } int BUI_cmp(const BUI a, const BUI b) { if (a->len > b->len) { return 1; } if (a->len len) { return -1; } for (int i = a->len - 1; i >= 0; --i) { if (a->num.data[i] > b->num.data[i]) { return 1; } if (a->num.data[i] num.data[i]) { return -1; } } return 0; } #define MAX(A, B) (A > B ? A : B) void BUI_add(BUI r, const BUI a, const BUI b) { Array c; new_Array(&c, MAX(a->len, b->len) + 1); memset(c.data, 0, c.len); memcpy(c.data, a->num.data, a->len); for (int i = 0; i len; ++i) { c.data[i] += b->num.data[i]; } for (int i = 0; i = 10) { c.data[i + 1] += c.data[i] / 10; c.data[i] %= 10; } } del_Array(&r->num); r->num = c; r->len = c.len; for (int i = r->num.len - 1; r->num.data[i--] == 0; --r->len); } void BUI_mul(BUI r, const BUI a, const BUI b) { BUI c; new_BUI(c, "0"); { BUI one; new_BUI(one, "1"); BUI i; new_BUI(i, "0"); for (; BUI_cmp(i, a) num); r->num = c->num; r->len = c->len; } void BUI_print(BUI b) { for (int i = b->len - 1; i >= 0; --i) { putchar(b->num.data[i] + '0'); } } int main(void) { BUI a; new_BUI(a, "123456789"); BUI b; new_BUI(b, "987654321"); BUI_print(a); fputs(" x ", stdout); BUI_print(b); fputs(" = ", stdout); time_t start_time = clock(); BUI_mul(a, a, b); time_t end_time = clock(); BUI_print(a); del_BUI(a); del_BUI(b); printf("\nelapsed time: %.3f\n", (double)(end_time - start_time) / CLOCKS_PER_SEC); printf("%d %d\n", a->num.len, a->len); return 0; } 

Test.java

 import java.util.*; class BUI { byte[] num; int len; BUI(String s) { len = s.length(); num = new byte[len]; for (int i = 0; i  b.len) { return 1; } if (len = 0; --i) { if (num[i] > b.num[i]) { return 1; } if (num[i] < b.num[i]) { return -1; } } return 0; } void add(BUI a, BUI b) { byte[] c = new byte[Math.max(a.len, b.len) + 1]; Arrays.fill(c, (byte)0); System.arraycopy(a.num, 0, c, 0, a.num.length); for (int i = 0; i < b.len; ++i) { c[i] += b.num[i]; } for (int i = 0; i = 10) { c[i + 1] += c[i] / 10; c[i] %= 10; } } num = c; len = c.length; for (int i = num.length - 1; num[i--] == 0; --len); } void mul(BUI a, BUI b) { BUI c = new BUI("0"); { BUI one = new BUI("1"); BUI i = new BUI("0"); for (; i.cmp(a) = 0; --i) { System.out.print(num[i]); } } } public class Test { public static void main(String[] args) { BUI a = new BUI("123456789"); BUI b = new BUI("987654321"); a.print(); System.out.print(" x "); b.print(); System.out.print(" = "); long start_time = System.currentTimeMillis(); a.mul(a, b); long end_time = System.currentTimeMillis(); a.print(); System.out.printf("\nelapsed time: %.3f\n", (end_time - start_time) / 1000.0); } } 

“语言:C(作为C ++的子集)”。

只是没有

C不是C ++的子集。 它们具有通用语法(大部分是C),但大多数运行时检查是不同的(取决于编译器),解释代码的方式不同(在少数情况下),并且为C编写的大多数代码都是非常糟糕的C ++。

C ++有很多工具可以加速你的算法实现(不确定多少,但如果你正确测量,你肯定会看到变化)。

举个简单的例子,使用std :: string作为字符数组。

我正在计划一个项目,其中有一个部分处理大量的计算。 我决定用C编写这个核心计算部分,但是有了这种结果,我宁可用Java编写所有东西;

去吧(也就是说,如果它对你来说更简单,用Java编写)。 您可以在C中获得更好的性能,就像您可以在Java中获得更好的性能一样。 由您决定在优化算法和代码时花费了多少时间和精力。

计算速度不会来自使用优化选项运行编译器 – 实际上,它确实存在,但与算法优化的速度相比,这相对较小。 通过在您熟悉的开发工具链中进行优化,然后绊倒您不熟悉的语言,您可以获得更快的速度。

它更容易,甚至更快? 我仍然相信C,所以如果我的代码有任何问题,请告诉我。 我的期望是Java版本应该慢1.5倍或更多,但它在某种程度上优于C.

你的逻辑是有缺陷的。

您编写的比较无法以任何方式代表C和Java之间的速度差异(在C ++和Java之间更低)。 它是比较这两种实现的代表,用不同的语言编译,而不是语言本身。

换句话说,比较这样的两个应用程序,即使它们看起来相同,也不会比较语言(或编译器)的速度。 它只是比较两个不同的程序,运行相同算法的不同版本。 这是一个特例。

您的C代码已编译。 它将在不同的硬件上具有不同的性能特征(例如,两个处理器与四个处理器)。

您的java代码是字节编译的。 它将在大多数Java VM运行之前进行优化,以最好地匹配您的代码将运行的平台。

最后,您可能在C或C ++中比在Java中更积极地进行优化,并且如果您需要编写真正的性能关键代码,那么您可能会获得无法与Java匹配的C或C ++代码,因为它将是比运行Java VM本身所需的速度阈值快。

这种优化虽然在特定情况下分析和优化需要花费大量时间和精力,但大多数应用程序域都不需要它。 如果你不知道你是否需要这种性能水平,你可能不会。

你的C版本有很多不必要的内存分配,这是相对昂贵的。 如果使用就地添加和增量function(见下文),您可以大幅提高性能:

  • 原始C代码= 200秒
  • 使用原位添加= 7.6秒
  • 使用原位加法和增量= 6.5秒

速度的差异完全来自减少分配数量,从原始代码中的2.5亿(!)到修改后的代码中的30。 因此,您的原始代码实际上只是测量每种语言的内存管理器的效率,而不是实际的bignum乘法算法。

您可以在Java代码中使用相同的优化,以获得类似的速度改进。 但要小心玩过基准/优化游戏。 在特定版本上有足够的工作量,你可能比其他版本更快。 您通常不应将您的语言决定仅仅基于“X快1%”

要使用新的就地function,只需将乘法循环更改为:

  for (; BUI_cmp(i, a) < 0; BUI_inc(i)) { BUI_addinplace(c, b); } 

使用的就地添加/增量function如下:

 void BUI_addinplace(BUI a, const BUI b) //a += b { int maxSize = MAX(a->len, b->len) + 1; if (a->num.len < maxSize) { Array tmp; new_Array(&tmp, maxSize); memset(tmp.data, 0, tmp.len); memcpy(tmp.data, a->num.data, a->len); del_Array(&a->num); a->num = tmp; } for (int i = 0; i < b->len; ++i) { a->num.data[i] += b->num.data[i]; } int maxLen = a->len; for (int i = 0; i < a->len; ++i) { if (a->num.data[i] >= 10) { a->num.data[i + 1] += a->num.data[i] / 10; a->num.data[i] %= 10; maxLen = i + 2; } } if (maxLen > a->len) a->len = maxLen; } void BUI_inc(BUI a) //a += 1 { int maxSize = a->len + 1; if (a->num.len < maxSize) { ++numAllocations; Array tmp; new_Array(&tmp, maxSize); memset(tmp.data, 0, tmp.len); memcpy(tmp.data, a->num.data, a->len); del_Array(&a->num); a->num = tmp; } ++a->num.data[0]; if (a->num.data[0] < 10) return; int maxLen = a->len; for (int i = 0; i < a->len; ++i) { if (a->num.data[i] >= 10) { a->num.data[i + 1] += a->num.data[i] / 10; a->num.data[i] %= 10; maxLen = i + 2; } else { break; } } if (maxLen > a->len) a->len = maxLen; } 

正如在评论和其他答案中提到的那样,瓶颈是对malloc的近2.5亿次调用。

我通常不写C,所以请原谅我的非惯用代码,但是这里有一个非常原始的分配器(它有许多限制,可能仍有错误和易于优化的机会并可能使用一些断言),它优于malloc在这种情况下很多。

 #define BUFFER_SIZE 1048576 //reserve 1MB, although we only use 417bytes typedef struct mem_block_hdr { unsigned short size; //size of the memory block, excluding header char free; //is this block free char cont; //not used }mem_block_hdr_t; struct _myallocdata { char mem[BUFFER_SIZE]; unsigned int highest_header_pos; } my_alloc_data; void init_block_hdr(void *mem) { mem_block_hdr_t *head = (mem_block_hdr_t*) mem; head->size = USHRT_MAX; head->free = 1; head->cont = 0; } void init_my_alloc() { init_block_hdr(my_alloc_data.mem); my_alloc_data.highest_header_pos = 0; } mem_block_hdr_t *next_header(mem_block_hdr_t *curr) { return (mem_block_hdr_t*) ((char*) curr + sizeof(mem_block_hdr_t) + curr->size); } mem_block_hdr_t *find_next_free(unsigned int size) { void * ret; char end_reached = 0; mem_block_hdr_t *head = (mem_block_hdr_t*) my_alloc_data.mem; while( (!head->free || head->size < size) && ((char*) head - my_alloc_data.mem ) < my_alloc_data.highest_header_pos ) { head = next_header(head); } return head; } void *my_alloc(unsigned int size) { mem_block_hdr_t *header = find_next_free(size); unsigned int diff = (char*) header - my_alloc_data.mem ; if (header->size == USHRT_MAX) { header->size = size; } header->free = 0; if (diff >= my_alloc_data.highest_header_pos) { mem_block_hdr_t *new_high = next_header(header); init_block_hdr(new_high); my_alloc_data.highest_header_pos = ((char*) new_high) - my_alloc_data.mem; } return (void *)++header; } void my_free(void *mem) { mem_block_hdr_t *hdr =(mem_block_hdr_t *) ((char *)mem - sizeof(mem_block_hdr_t)); hdr->free = 1; } void new_Array(Array *a, int len) { //a->data = (byte *) malloc(len * sizeof(byte)); a->data = (byte *) my_alloc(len * sizeof(byte)); a->len = len; } void del_Array(Array *a) { //free(a->data); my_free(a->data); } //calling init_my_alloc() in main before using it 

我得到的数字是:

 123456789 x 987654321 = 121932631112635269 elapsed time (custom alloc): 25.546 19 18 123456789 x 987654321 = 121932631112635269 elapsed time (malloc): 290.118 19 18 

编辑:好像我选择了对malloc调用不太友好的MSVC标志,使用gcc我得到了这个:

 123456789 x 987654321 = 121932631112635269 elapsed time (custom alloc): 30.703 19 18 123456789 x 987654321 = 121932631112635269 elapsed time (malloc): 46.406 19 18 

我愿意打赌那里有很多分配器实现(因为有时似乎每个更大的C项目都有自己的少数)。

无论如何,如果你觉得编写Java更加舒服,并且你认为它足以满足你的目的,那么你没有理由不使用它。 毕竟编程语言只是工具。