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 그러게요, 제시하신 코드가 더 깔끔하군요. ^^
정성태

... 76  77  78  79  80  81  82  83  84  85  86  87  88  [89]  90  ...
NoWriterDateCnt.TitleFile(s)
11710정성태10/2/201822058.NET Framework: 794. C# - 같은 모양, 다른 값의 한글 자음을 비교하는 호환 분해 [5]
11709정성태9/30/201820424개발 환경 구성: 402. .NET Core 콘솔 응용 프로그램을 docker로 실행/디버깅하는 방법 [1]
11708정성태9/30/201822628개발 환경 구성: 401. .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 [2]파일 다운로드1
11707정성태9/30/201823947오류 유형: 489. ASP.NET Core를 docker에서 실행 시 "Failed with a critical error." 오류 발생 [1]
11706정성태9/29/201820097개발 환경 구성: 400. Synology NAS(DS216+II)에서 실행한 gcc의 Segmentation fault [2]
11705정성태9/29/201820958개발 환경 구성: 399. Synology NAS(DS216+II)에 gcc 컴파일러 설치
11704정성태9/29/201824973기타: 73. Synology NAS 신호음(beep) 끄기 [1]파일 다운로드1
11703정성태9/27/201819701개발 환경 구성: 398. Blazor 환경 구성 후 빌드 속도가 너무 느리다면? [2]
11702정성태9/26/201816906사물인터넷: 44. 넷두이노(Netduino)의 네트워크 설정 방법
11701정성태9/26/201822642개발 환경 구성: 397. 공유기를 일반 허브로 활용하는 방법파일 다운로드1
11700정성태9/21/201820726Graphics: 25. Unity - shader의 직교 투영(Orthographic projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
11699정성태9/21/201819210오류 유형: 488. Add-AzureAccount 실행 시 "No subscriptions are associated with the logged in account in Azure Service Management (RDFE)." 오류
11698정성태9/21/201820532오류 유형: 487. 윈도우 성능 데이터를 원격 SQL에 저장하는 경우 "Call to SQLAllocConnect failed with %1." 오류 발생
11697정성태9/20/201819398Graphics: 24. Unity - unity_CameraWorldClipPlanes 내장 변수 의미
11696정성태9/19/201820301.NET Framework: 793. C# - REST API를 이용해 NuGet 저장소 제어파일 다운로드1
11695정성태9/19/201825462Graphics: 23. Unity - shader의 원근 투영(Perspective projection) 행렬(UNITY_MATRIX_P)을 수작업으로 구성
11694정성태9/17/201819678오류 유형: 486. nuget push 호출 시 405 Method Not Allowed 오류 발생
11693정성태9/16/201822869VS.NET IDE: 128. Unity - shader 코드 디버깅 방법
11692정성태9/13/201823143Graphics: 22. Unity - shader의 Camera matrix(UNITY_MATRIX_V)를 수작업으로 구성
11691정성태9/13/201820086VS.NET IDE: 127. Visual C++ / x64 환경에서 inline-assembly를 매크로 어셈블리로 대체하는 방법 - 두 번째 이야기
11690정성태9/13/201823060사물인터넷: 43. 555 타이머의 단안정 모드파일 다운로드1
11689정성태9/13/201822355VS.NET IDE: 126. 디컴파일된 소스에 탐색을 사용하도록 설정(Enable navigation to decompiled sources)
11688정성태9/11/201817706오류 유형: 485. iisreset - The data is invalid. (2147942413, 8007000d) 오류 발생
11687정성태9/11/201819583사물인터넷: 42. 사물인터넷 - 트랜지스터 다중 전압 테스트파일 다운로드1
11686정성태9/8/201818644사물인터넷: 41. 다중 전원의 소스를 가진 회로파일 다운로드1
11685정성태9/6/201818603사물인터넷: 40. 이어폰 소리를 capacitor로 필터링파일 다운로드1
... 76  77  78  79  80  81  82  83  84  85  86  87  88  [89]  90  ...