Java가 C보다 2배 이상 빠릅니다....

denmark114의 이미지

저 지금 굉장히 당황해있습니다... 어디선가 이런저런 이유로 단순 연산 위주 프로그램에선 Java가 C/C++보다 더 빠를수있단 글을 보고 설마 그러겠냔 마음으로 테스트용 뻘코드를 짜봤습니다. 그냥 곱셈을 매우매우 비효율적으로 하는 코든데, 여튼 똑같은 알고리즘으로 문법만 달리해서 C 버전과 Java 버전을 짜서 돌려봤는데 아무리 여러번 실행시켜도 Java가 2배 이상 빠르게 나옵니다..

아래는 결과입니다.

운영체제: Windows 7
언어: C (C++의 부분집합으로서의 C)

컴파일러: Visual C++
최적화 옵션: /Ox /Oi /Ot /Oy /GL
실행시간 (초): 40 +/- 1

컴파일러: MinGW/gcc
최적화 옵션: -O3 march=native
실행시간 (초): 81 +/- 1

컴파일러: MinGW/g++
최적화 옵션: -O3 march=native
실행시간 (초): 81 +/- 1

언어: Java

컴파일러: Oracle JDK
VM: Oracle JVM
실행시간(초): 18 +/- 1

여담으로 윈도우용 gcc는 성능이 매우 안좋네요.. 밑은 코드입니다. 제가 C를 잘못짜놓은거겠죠? 이상한데 있으면 꼭 지적부탁드립니다. 정말 지금 똑같은 테스트를 아무리 여러번 돌려봐도 결과가 달라지지가 않는데 믿을수가 없습니다. 사실 이번에 프로젝트로 준비하는게 마침 연산을 매우 하드하게 하는거라서, 연산처리부분을 C로 멋있게 짜보려고 했는데 계획을 달리해야하려나요.. 이정도로 Java가 빠르다면 굳이 힘들게 디버깅해가면서 C로 짤 필요가 없는것같네요.

Test.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
 
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 < len; ++i)
	{
		b->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 < b->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] < b->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 < b->len; ++i)
	{
		c.data[i] += b->num.data[i];
	}
	for (int i = 0; i < c.len - 1; ++i)
	{
		if (c.data[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) < 0; BUI_add(i, i, one))
		{
			BUI_add(c, c, b);
		}
		del_BUI(one);
		del_BUI(i);
	}
	del_Array(&r->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("%u %u\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 < len; ++i)
		{
			num[i] = (byte)Character.getNumericValue(s.charAt(len - i - 1));
		}
	}
 
	int cmp(BUI b)
	{
		if (len > b.len)
		{
			return 1;
		}
		if (len < b.len)
		{
			return -1;
		}
		for (int i = len - 1; i >= 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 < c.length - 1; ++i)
		{
			if (c[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.add(i, one))
			{
				c.add(c, b);
			}
		}
		num = c.num;
		len = c.len;
	}
 
	void print()
	{
		for (int i = len - 1; i >= 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);
	}
}
emptynote의 이미지

JDK 1.4 NIO 는 역설적으로 자바의 한계를 고백한겁니다.

java 1.4 NIO 가 필요 없습니까?

아닙니다. 현재 자동차 네비는 가는 길을 알려주지만 아직까지 모든 경우의 수에서 가장 빠른길을 알려주지는 않습니다.

JDK 1.2 IO 가 바로 그러합니다.

JDK 1.2 에 NIO 는 없습니다.

대부분의 OS 가 c 언어로 만들어져 있고 IO 효율에 대해서 지금까지도 고민을 하고 있습니다.

c 언어 입장에서 자바는 겨우 jdk 1.4 가 되어서야 NIO 도입으로 조금 따라 온겁니다.

마지막으로 저 같은 길치한테 네비는 축복입니다.

왜냐하면 목적지 도착이 중요하니깐요.

emptynote의 이미지

진짜로 중요한것은 언어가 빠른것이 아닌 내 자신의 실력 아닐까요?

저는 신놀이 개발자입니다.(참고 : www.sinnori.pe.kr)

비동기 서버와 소켓 폴인 접속 API 로 echo 메시지라고 int randomInt, long startTime 2개 값을 갖는데요.

서버와 클라이언트는 local PC 에서 돌아갑니다.

클라이언트에서 값을 세팅후 서버로 보내고 받은 시간을 측정하면 보통 2ms 나옵니다.

로컬이 아닌 내부망에 있는 PC 에서 서버를 돌리면 이 시간은 더 길어집니다.

제가 신놀이를 만들었지만 저는 왜 이렇게 오래 걸리는지 알수없습니다.

조금 특이사항으로 웹에서 손으로 클릭할때에는 나오지 않더 0 ms 가 JMeter 를 이용하는 순간 아주 가끔이지만 나오는거을 확인했습니다.

아마도 쓰레드를 깨우는 시간에 대한 비용이 있지 않을까 조심스럽게 추측만 하고 있습니다.

제가 세운 가설에 대한 검증하는데 자바가 c 언어 보다 빠르다는 사실이 도움이 되나요?

쓰레드 세이프가 깨지면 내가 보낸 메시지에 대한 응답 메시지가 아닌 다른 사람이 보낸 메시지에 대한 응답 메시지를 받을 수 있지요.

언어간의 빠르다 느리다 라는 사실은 쓰레드 세이프함을 보장해 주지 않습니다.

simminjo의 이미지

C언어 버젼.......재미삼아 최적화 해보니...
현재 코드 기준으로 서버에서 29초 걸리던것이 현재는 19초 나오고 결과는 동일하게 나오네요.

그냥 대충 눈에 보이는것들만 최적화 한 것들이라....고민 더 하면 더 빨라질수도 있지않을까 싶기도하네요

---------------------------------------------------------------
Opensource에 기여하는 것이 꿈입니다.
내가 만든 코드를 모두가 사용할 때 까지~

simminjo의 이미지

집에서 테스트 해본결과.....
최초 gcc로 그냥 빌드시에 94초
-O3 옵션만 추가했을시에 76초
기타 코드 수정 했을시에 12초대가 나오네요...

결국은 코드를 고치면 빨라지는 거였네요.

윈도우즈용 gcc 3.4.2 (mingw) 입니다....

---------------------------------------------------------------
Opensource에 기여하는 것이 꿈입니다.
내가 만든 코드를 모두가 사용할 때 까지~

klenui의 이미지

예전에 수치연산을 많이하는 시뮬레이터를 만든적이 있는데, 자바로 하루가 걸려도 안나오던 답이 C++로 한시간만에 답이 나온적이 있습니다. 자바도 나름 신경 써서 짰는데(객체 할당 최소화등) 이런 결과가 나와서 그 이후로는 컨셉확인 용도 외는 자바를 사용하지 않습니다.

올려주신 코드는 실제로 프로파일링을 해봐야 원인을 알수 있겠습니다만, 일반적으로 mingw가 vc++보다 컴파일 속도는 떨어져도 그 결과물은 더 빠르다라는 말을 stackoverflow등에서 심심찮게 볼수있는 상황에서 올려주신 예제는 여로모로 논쟁적인 경우같네요. 제가 프로파일링할 짬은 없고 누군가 해주시길 기다리고 있습니다.

simminjo의 이미지

최적화 시도해본 내용중......잔챙이들 빼고 딱 한가지만 뽑으라면....
malloc, free 가 성능을 죽이는 최고의 요인이었습니다.

해당 부분을 정적 사이즈로 잡아놓고 실행만 시켜도 시간은 반......보다 훨씬 더 많이 빨라집니다..

malloc, free와 같은 동적 메모리 관리의 경우 메모리를 할당받고 반환하는 과정에서 매우 많은 시간이 소모됩니다.
그러한 작업이 루프내에 계속 반복되니 당연히 속도는 저하 될 수 밖에 없습니다.

잔챙이 코드 건드리려다가 그냥 다 롤백시키고 위의 부분만 적용된 버젼입니다.
=====================================================================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
 
typedef signed char byte;
 
typedef struct _Array
{
	byte data[30];
	int len;
}
Array;
 
typedef struct _BUI
{
	Array num;
	int len;
}
BUI[1];
 
void new_BUI(BUI b, const char *s)
{
	int len = strlen(s);
	b->len = b->num.len = len;
	for (int i = 0; i < len; ++i)
	{
		b->num.data[i] = s[len - i - 1] - '0';
	}
}
 
int BUI_cmp(const BUI a, const BUI b)
{
	if (a->len > b->len)
	{
		return 1;
	}
	if (a->len < b->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] < b->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;
	c.len =  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 < b->len; ++i)
	{
		c.data[i] += b->num.data[i];
	}
	for (int i = 0; i < c.len - 1; ++i)
	{
		if (c.data[i] >= 10)
		{
			c.data[i + 1] += c.data[i] / 10;
			c.data[i] %= 10;
		}
	}
	r->num = c;
	r->len = c.len;
	for (int i = r->num.len - 1; !(r->num.data[i--]) ; --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) < 0; BUI_add(i, i, one))
		{
			BUI_add(c, c, b);
		}
	}
	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);
	printf("\nelapsed time: %.3f\n", (double)(end_time - start_time) / CLOCKS_PER_SEC);
	printf("%u %u\n", a->num.len, a->len);
	return 0;
}

---------------------------------------------------------------
Opensource에 기여하는 것이 꿈입니다.
내가 만든 코드를 모두가 사용할 때 까지~

dolsemix의 이미지

C의 strlen 함수도 병목인듯 합니다.
자바 String obj 정도면 len 정도는 내장하고 있겠죠...

담배 고만 펴야겠다...

체스맨의 이미지

자바와 C/C++ 성능 차이는 과거에 상당한 논쟁이 돼 오긴 했는데요.
제대로 최적화하지 않은 상태에서, 특히나 언어간 성능 비교를 섣불리 하는 것은 좋지 않습니다.
C가 스크립트 언어도 아니고 동일 머쉰에서 동일 구현이 2배이상 빠를 수가 없어요.

http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html
2000년대 초에 1위를 달리던 자바가 왜 결국은 C에 뒤져있는지 생각해보시기 바랍니다.

Orion Project : http://orionids.org

powersys의 이미지

보통 속도가 역전 되었다는경우..

동일작업의 코드가 아니거나.. 컴파일러버전이 차이가 있는경우가 있더군요..

자바vm 경우 C로 컴파일 되기때문에 C컴파일러가 발전되면.. 자바는 자연스레 발전됩니다. 컴파일만 새로 하는것으로도 말이죠..

현재까지 역전된 속도를 본적은없었네요..