며칠 전, c#/.net이 너무 느리고 c#/.net의 일부 기능을 삭제하라고 요구하는 N개의 기사가 게시되었습니다.
이러한 기사에 관계없이 c#/.net이 느리다는 것은 업계에서 인정하는 철칙인 것 같습니다. c#/.net이 C++보다 그다지 느리지 않다는 것을 모든 사람이 증명하더라도 응용 프로그램 수준 성능은 여전히 너무 느립니다. .
그렇다면 C#/.net은 어디에서 느린가요?
불행하게도 대부분의 프로그래머는 대부분의 C# 프로그램 속도를 저하시킵니다. 이러한 결론은 받아들이기 쉽지 않을 수도 있지만 널리 퍼져 있습니다.
문자열 연산
거의 모든 프로그램에는 문자열 연산이 있으며 그 중 적어도 90%는 대소문자 비교를 무시해야 합니다. 그 중 적어도 절반에는 다음과 유사한 코드가 있습니다.
if (str1.ToUpper() == str2.ToUpper())
또는 ToLower 버전에는 다음과 같은 Web HttpModule이 있는 것도 보았습니다.
for (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
//...
생각해 보십시오. 페이지가 요청될 때마다 이러한 코드 조각을 실행하여 큰 영역의 문자열 인스턴스를 생성해야 합니다. 더욱 과장된 것은 이것이 시간과 공간을 교환하는 것이라고 말하는 사람들도 있다는 것입니다. . .
성능 테스트
이 방법이 느리다고 하면 일부 사람들은 이를 인정하지 않고 이것이 최선의 방법이라고 생각할 수도 있으므로 여기서는 사실을 증명하기 위해 구체적인 테스트를 사용해야 합니다.
먼저 성능을 테스트할 방법을 준비합니다.
private static TResult MeasurePerformance<TArg, TResult>(Func<TArg, TResult> func, TArg arg, int loop)
{
GC.수집();
int gc0 = GC.CollectionCount(0);
int gc1 = GC.CollectionCount(1);
int gc2 = GC.CollectionCount(2);
TResult 결과 = default(TResult);
스톱워치 sw = Stopwatch.StartNew();
for(int i = 0; i < 루프; i++)
{
결과 = func(arg);
}
Console.WriteLine(sw.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine("GC 0:" + (GC.CollectionCount(0) - gc0).ToString());
Console.WriteLine("GC 1:" + (GC.CollectionCount(1) - gc1).ToString());
Console.WriteLine("GC 2:" + (GC.CollectionCount(2) - gc2).ToString());
결과 반환;
}
그런 다음 힙 문자열을 준비합니다.
개인 정적 목록<문자열> CreateStrings()
{
List<string> strs = new List<string>(10000);
char[] chs = 새로운 char[3];
for (int i = 0; i < 10000; i++)
{
int j = i;
for(int k = 0; k < chs.Length; k++)
{
chs[k] = (char)('a' + j % 26);
j = j / 26;
}
strs.Add(새 문자열(chs));
}
문자열을 반환;
}
그런 다음 ToUpper 구현을 살펴보겠습니다.
private static bool ImplementByToUpper(List<string> 문자열, 문자열 값)
{
for (int i = 0; i < strs.Count; i++)
if (value.ToUpper() == strs[i].ToUpper())
사실을 반환;
거짓을 반환;
}
마지막으로 기본 메서드를 준비합니다.
List<string> strs = CreateStrings();
부울 결과;
Console.WriteLine("ImplementByToUpper 사용");
result = MeasurePerformance(s => ImplementByToUpper(strs, s), "yZh", 1000);
Console.WriteLine("결과는 " + result.ToString());
Console.ReadLine();
실행 결과를 살펴보겠습니다.
ImplementByToUpper 사용
2192ms
GC 0:247
GC 1:0
GC 2:0
결과는 참이다
비교 테스트를 수행하고 string.Equals를 사용하여 테스트해 보겠습니다.
private static bool ImplementByStringEquals(List<string> 문자열, 문자열 값)
{
for (int i = 0; i < strs.Count; i++)
if (string.Equals(value, strs[i], StringComparison.CurrentCultureIgnoreCase))
사실을 반환;
거짓을 반환;
}
실행 결과를 살펴보겠습니다.
ImplementByStringEquals 사용
1117ms
GC 0:0
GC 1:0
GC 2:0
결과는 참이다
이에 비해 ToUpper를 사용하면 속도가 두 배 더 느리고 0세대 가비지 개체가 많습니다. 시간과 공간을 교환한다고 주장하는 사람들은 공간과 교환하여 무엇을 얻었는지 생각해 볼 수 있습니다. 부정적인 시간?
사전 클래스 사용
문자열 시나리오를 계속 진행하면 일부 사람들은 속도를 높이기 위해 해시 테이블 및 기타 유사한 구조를 사용할 수도 있습니다. 예, 이는 좋은 생각이지만 해시 테이블이 항상 최선의 솔루션은 아닙니다. 테스트를 해보자:
private static bool ImplementByHashSet(List<string> 문자열, 문자열 값)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
return set.Contains(값);
}
실행 결과를 살펴보십시오.
ImplementByHashSet 사용
5114ms
GC 0:38
고전 1:38
고전 2:38
결과는 참이다
놀랍게도 속도는 ToUpper를 사용할 때보다 2배 이상 느리고, 2세대 가비지 수집도 38회 수집된다(2세대 가비지 수집 실행 시 1세대와 0세대 가비지 수집을 강제로 수행하게 된다).
그러나 처리 속도를 높이기 위해 해시 테이블 등을 사용한다는 생각은 매우 올바른 생각이지만 해시 테이블 자체를 캐시할 수 있다는 전제가 있다. 예를 들면 다음과 같다.
private static Func<string, bool> ImplementByHashSet2(List<string> strs)
{
HashSet<string> set = new HashSet<string>(strs, StringComparer.CurrentCultureIgnoreCase);
반환 세트.포함;
}
그런 다음 기본 메서드를 다음과 같이 수정합니다.
Console.WriteLine("ImplementByHashSet2 사용");
결과 = MeasurePerformance(s =>
{
var f = ImplementByHashSet2(strs);
bool ret = 거짓;
for (int i = 0; i < 1000; i++)
{
ret = f(s);
}
반환 ret;
}, "yZh", 1);
Console.WriteLine("결과는 " + result.ToString());
Console.ReadLine();
결과를 살펴보겠습니다.
ImplementByHashSet2 사용
6ms
GC 0:0
GC 1:0
GC 2:0
결과는 참이다
성능이 대폭 향상되었습니다.
더
C#/.net 속도를 늦추는 것은 무엇입니까? 간단히 말하면: 불필요한 개체 생성, 불필요한 동기화, 비효율적인 루프 실행 방법(예: Firelong에서 비판을 받았지만 ms에서는 루프에서 Invoke를 사용할 수 없는 리플렉션), 비효율적인 데이터 구조 및 알고리즘 사용(보기) 캐싱의 경우 해시 테이블과 유사한 구조의 놀라운 성능을 보면 그 차이를 알 수 있습니다.)
c#/.net의 낮은 임계값은 어느 정도 더 많은 프로그래머를 c#/.net으로 끌어들이는 데 도움이 되지만 전체 c#/.net 프로그램의 코드 수준도 많이 감소하므로 사람들은 이를 매우 걱정합니다.
마지막으로, 시스템의 성능은 시스템의 가장 성능이 좋은 부분이 아니라 시스템의 가장 성능이 떨어지는 부분에 의해 결정된다는 점을 잊지 마십시오. 16g 메모리, 100t 하드 드라이브, 최고급 그래픽 카드를 탑재했지만 386 CPU가 없는 이 컴퓨터의 성능은 386의 성능이다. 마찬가지로 C#/.net이 아무리 좋아도 프로그래머의 기술이 부족하면 작성된 프로그램의 성능도 당연히 떨어지게 됩니다.