Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

C# - 10진수 숫자를 담은 문자열을 숫자로 변환하는 방법

아래와 같은 질문이 있군요.

아스키로 구성된 바이트를 long으로 변환 문의
; https://www.sysnet.pe.kr/3/0/5664

정리해 보면, 숫자를 "문자열"로 가지고 있는 데이터를 다시 숫자로 변환하고 싶은 건데요, 가령 "0000000123" 문자열이 보관된 byte 배열을,

byte [] bytes = /* 소켓 Receive로 받은 바이트 배열 */ Encoding.ASCII.GetBytes("0000000123");

숫자 123으로 변환하고 싶은 것입니다.

string tmp = Encoding.ASCII.GetString(bytes);
long myint = Convert.ToInt64(tmp);

질문자는 위에 대한 코드보다 더 빠른 것을 원하는 건데요, 사실 이런 경우, BCL에서 제공하는 메서드를 다루는 것이 최적화가 잘 된 코드라서 대부분은 충분히 빠르므로 그냥 쓰셔도 좋습니다.

굳이, 아주 미세하게 빠른 속도를 원한다면 GetString 절차를 생략하고 곧바로 바이트 배열로부터 숫자를 바꿀 수 있을 텐데요, 대충 다음과 같은 식으로 만들 수 있습니다.

private static long ConvertToLong(byte[] bytes)
{
    long n = 1;
    long result = 0;

    for (int i = bytes.Length - 1; i >= 0; i --)
    {
        result = result + (bytes[i] - '0') * n;
        n *= 10;
    }

    return result;
}

성능 측정을 해보면 딱히 "감동적인" 수준으로 빠르지는 않습니다. 어쨌든 이렇게 테스트 코드를 구성해서,

using System.Diagnostics;
using System.Text;

internal class Program
{
    static void Main(string[] args)
    {
        Action<int, string, Func<byte[], long>, byte[]> action = (loopCount, title, work, arg) =>
        {
            Stopwatch st = new Stopwatch();
            st.Start();

            Random rand = new Random(Environment.TickCount);

            for (int i = 0; i < loopCount; i++)
            {
                work(arg);
            }

            st.Stop();

            Console.WriteLine(title + " : " + st.ElapsedMilliseconds);
        };

        byte[] buf = Encoding.ASCII.GetBytes("1000000123");

        action(1, "touch-JIT", ConvertNormal, buf);
        action(1, "touch-JIT", ConvertToLong, buf);

        Console.WriteLine();

        action(1000000, "ConvertNormal", ConvertNormal, buf);
        action(1000000, "ConvertToLong", ConvertToLong, buf);
    }

    private static long ConvertNormal(byte[] bytes)
    {
        string tmp = Encoding.ASCII.GetString(bytes);
        return Convert.ToInt64(tmp);
    }
    
    // ...[생략 ConvertToLong]...
}

실행하면 이런 결과가 나옵니다.

ConvertNormal : 36
ConvertToLong : 12

수치상으로 3배 빨라지긴 했는데, 백만 번 수행한 속도 차이이기 때문에 웬만한 고성능을 요구하는 경우가 아니라면 차이는 미미합니다. 게다가, 저렇게나 성능이 요구되는 시스템이라면 애당초 서버 측에 "숫자 문자열"이 아닌, "숫자"로 데이터를 정정해서 보내달라고 하는 편이 더 효과를 거둘 수 있습니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 5/11/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2022-05-11 09시41분
[차가워] 와우 고맙습니다.
서버단을 못 건드리는 상황에서 빠른 처리가 필요할때 정말 요긴합니다.
감사합니다.
[guest]
2022-05-11 10시06분
[차가워] 그런데 선생님 pc는 매우 빠르시네요.
저도 위의 코드를 돌려보니
77
17
나오네요.
제pc도 최신cpu인데 시간차 크네요.
[guest]
2022-05-11 10시20분
"인텔 코어i9-12세대 12900K"입니다. (별도 튜닝은 하지 않았습니다.)

2021년 새로운 PC
; https://www.sysnet.pe.kr/0/0/535

그나저나, 본문에서는 Debug 빌드로 한 것인데, Release 빌드로 돌리니 좀 더 빠르긴 하군요. ^^;

ConvertNormal : 33
ConvertToLong : 6
정성태
2022-05-11 11시16분
[차가워] 충격 !!
저도 12900K @5.2 고정
DDR4 3600 CL14

제가 두배 이상 늦네요.
문제가 뭘까요? ㅠㅠ
[guest]
2022-05-11 11시44분
[차가워] 선생님이랑 저랑 속도 차이가 심하게 나는데요.
혹시 avx 명령어랑 관련 있을가요?
[guest]
2022-05-12 09시13분
글쎄요, AVX 명령어라고 해도 어차피 JIT 컴파일러에서 그 명령어를 사용하도록 번역해야 하는데, 그런 환경이라면 @차가워 님이랑 별다를 바가 없을 듯합니다.

아마도, 보드 상에서 기본 적용된 CPU나 메모리의 최적화 변수가 다른 것이 아닐까요?
정성태
2022-05-12 07시17분
[차가워] 이상하네요.
12900k pc가 5대 있는데요. 5대 모두 테스트 해보니 연산 소요시간이 비슷하게 나옵니다.
12900k@5.4 ddr5@6000 cl28 이 pc가 73, 16으로 제일 잘 나옵니다.

 private void button4_Click(object sender, EventArgs e)
        {
            Main2();
        }

        void Main2()
        {
            Action<int, string, Func<byte[], long>, byte[]> action = (loopCount, title, work, arg) =>
            {
                Stopwatch st = new Stopwatch();
                st.Start();

                Random rand = new Random(Environment.TickCount);

                for (int i = 0; i < loopCount; i++)
                {
                    work(arg);
                }

                st.Stop();

                Console.WriteLine(title + " : " + st.ElapsedMilliseconds);
            };

            byte[] buf = Encoding.ASCII.GetBytes("1000000123");

            action(1, "touch-JIT", ConvertNormal, buf);
            action(1, "touch-JIT", ConvertToLong, buf);

            Console.WriteLine();

            action(1000000, "ConvertNormal", ConvertNormal, buf);
            action(1000000, "ConvertToLong", ConvertToLong, buf);
        }

        long ConvertNormal(byte[] bytes)
        {
            string tmp = Encoding.ASCII.GetString(bytes);
            return Convert.ToInt64(tmp);
        }

        long ConvertToLong(byte[] bytes)
        {
            long n = 1;
            long result = 0;

            for (int i = bytes.Length - 1; i >= 0; i--)
            {
                result = result + (bytes[i] - '0') * n;
                n *= 10;
            }

            return result;
        }
[guest]
2022-05-12 09시03분
@차가워 Release 빌드로 테스트 하신 거 맞나요? 제가 가진 "AMD Ryzen 7 PRO 4750G"로도 다음의 성능이 나옵니다.

ConvertNormal : 62
ConvertToLong : 9
정성태
2022-05-12 09시56분
[차가워] 12900k@5.4
ddr5@6000 cl28     
ASUS MAXIMUS Z690 EXTREME
e코어 전부 끔, 하이퍼스레딩 전부 끔

닷넷프레임워크 4.8
릴리즈모드, 64비트, 최적화체크
ConvertNormal : 67
ConvertToLong : 5

ConvertNormal 값이 선생님 보다 너무 안나오네요.
[guest]
2022-05-29 04시02분
[al6uiz] .NET Core, .NET 6이상을 사용한다면 Span과 Utf8Parser를 이용해도 성능은 비슷(아주 조금 느린)하지만 깔끔한 코드가 될 것 같네요.

private static long ConvertToLongSpan(byte[] bytes)
{
  Utf8Parser.TryParse(bytes.AsSpan(), out long value, out var count);
  return value;
}
[guest]
2022-05-29 11시09분
@al6uiz 그러게요, 제시하신 코드가 더 깔끔하군요. ^^
정성태

... [121]  122  123  124  125  126  127  128  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
10900정성태2/19/201622104.NET Framework: 548. Linq는 결국 메서드 호출! [3]파일 다운로드1
10899정성태2/17/201623406개발 환경 구성: 282. kernel32.dll, kernel32legacy.dll, api-ms-win-core-sysinfo-l1-2-0.dll [1]
10898정성태2/17/201621939.NET Framework: 547. PerformanceCounter의 InstanceName 지정 시 주의 사항파일 다운로드1
10897정성태2/17/201621275디버깅 기술: 76. windbg 분석 사례 - 닷넷 프로파일러의 GC 콜백 부하
10896정성태2/17/201622385오류 유형: 320. FATAL: 28000: no pg_hba.conf entry for host "fe80::1970:8120:695:a41e%12"
10895정성태2/17/201621216.NET Framework: 546. System.AppDomain으로부터 .NET Profiler의 AppDomainID 구하는 방법 [1]
10894정성태2/17/201621902오류 유형: 319. Visual Studio에서 찾기는 성공하지만 해당 소스 코드 정보가 보이지 않는 경우
10893정성태2/16/201620616.NET Framework: 545. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까? - 두 번째 이야기
10892정성태2/16/201621159오류 유형: 318. 탐색기에서 폴더 생성/삭제 시 몇 초 동안 멈추는 현상
10891정성태2/16/201624236VC++: 95. 내 CPU가 MPX/SGX를 지원할까요? [1]
10890정성태2/15/201624084.NET Framework: 544. C# 5의 Caller Info를 .NET 4.5 미만의 응용 프로그램에 적용하는 방법 [5]
10889정성태2/14/201620413.NET Framework: 543. C++의 inline asm 사용을 .NET으로 포팅하는 방법 - 두 번째 이야기파일 다운로드1
10888정성태2/14/201618721.NET Framework: 542. 닷넷 - 특정 클래스가 로드되었는지 여부를 알 수 있을까?
10887정성태2/3/201619423VC++: 94. MPX(Memory Protection Extensions) 테스트파일 다운로드1
10886정성태2/3/201620664개발 환경 구성: 281. Intel MPX Runtime Driver 수동 설치
10885정성태2/2/201620359오류 유형: 317. Sybase.Data.AseClient.AseException: The command has timed out.
10884정성태1/11/201621585개발 환경 구성: 280. 닷넷에서 SAP Adaptive Server Enterprise 데이터베이스 사용파일 다운로드1
10882정성태1/6/201620872Windows: 113. 윈도우의 2179, 26143, 47001 TCP 포트 사용 [1]
10881정성태1/3/201622243오류 유형: 316. 윈도우 10 - 바탕/돋음 체가 사라져 한글이 깨지는 현상 [2]
10880정성태12/16/201520002오류 유형: 315. 닷넷 프로파일러의 오류 코드 정보
10879정성태12/16/201521949오류 유형: 314. Error : DEP0700 : Registration of the app failed. error 0x80070005
10878정성태12/9/201525018디버깅 기술: 75. UWP(유니버설 윈도우 플랫폼) 앱에서 global::System.Diagnostics.Debugger.Break 예외 발생 시 대응 방법
10877정성태12/9/201529375VC++: 93. std::thread 사용 시 R6010 오류 [2]
10876정성태11/26/201525498.NET Framework: 541. SignedXml을 이용한 ds:Signature만드는 방법 [3]파일 다운로드1
10875정성태11/26/201530463개발 환경 구성: 279. signtool.exe의 다중 서명 기능 [2]
10874정성태11/26/201526431개발 환경 구성: 278. 인증서와 인증서를 이용한 코드 사인의 해시 구분
... [121]  122  123  124  125  126  127  128  129  130  131  132  133  134  135  ...