C# listviewitem add 속도 관련!!

안녕하세요. 그간 몇가지의 질문을 올리고싶었지만, 하루만 더 하루만 더 찾아보고 반복해서 해결을 해왔습니다. 질문을 올리기가 뭔가 조심스럽네요…!

오늘 여쭤보고자 각잡고 글을 쓰는 질문은 제 실력으로는 도저히 가능한지, 불가능한지 판단조차 서지 않아 우선은 가볍게… 가능여부를 좀 여쭈어보고 싶어서입니다.

일단은 프로그램이 조금 난잡한 부분이 있습니다. 워낙 초보인데다… 짜집기로 만들다보니…

주 기능은, 실시간으로 올라오는 텍스트파일을 “한 줄씩” 읽고 계산해서 반영해주는 프로그램입니다.
수백kb단위의 용량은 더이상 빠를 필요가 없을 정도로 잘 계산해주고 있는데,
1-2분만에 100메가에 근접하는 정도의 파일 처리는 너무 느리더라구요.
라인수로는 1-2분만에 약 100만줄가량 생성이 됩니다.

대미지를줬습니다Regex = new Regex(timeStampRegex + "(치명타! )?((?<name>.+)[이가] )?(((?<skill>.+)[을를] 사용해 )|공격을 반사하여 )?(?<target>.+)(에게|[이가]) (?<damage>[^a-zA-Z]+)의 (|치명적인 )대미지를 (줬습니다|주고 문양 각인 효과가 발생|(받고|입고) 일부 강화 마법이 제거됐습니다)", RegexOptions.Compiled);

대미지를받았습니다Regex = new Regex(timeStampRegex + "(치명타! )?((?<name>.+)[이가] )?((?<from>.+)에게서 |(?<skill>.+)의 효과로 )(?<damage>[^a-zA-Z]+)의 (치명적인 )?((중독|출혈) )?대미지를 받았습니다", RegexOptions.Compiled);
                

위같은 정규식으로 처리하는 항목이 20여개정도되고

public void AddHeal(RecoverEventArgs e)
{
    this.HealAmount += e.Amount;
    if (Class == ClassType.NONE)
    {
        Class = SkillDictionary.GetClass(e.Skill);
    }
    this.HealLogList.Add(e.log);
    HealSkillEntity entity = null;
    foreach (HealSkillEntity hse in HealList)
    {
        if (hse.Target == e.Name)
        {
            entity = hse;
            break;
        }
    }
    if (entity == null)
    {
        entity = new HealSkillEntity();
        entity.Target = e.Name;
        this.HealList.Add(entity);
    }
    entity.TotalRecover += e.Amount;
    entity.Count++;
}

위 같은 항목들이 모여서

public void UpdataListView2()
{
    dd1 = ((double)NewTotalDealCount / (double)TotalDealTime); //22-05-04 두줄 새공비 추가
    rd3 = Math.Round(dd1, 2);

    if (ListItem.SubItems[1].Text == "")
    {
        ListItem.SubItems[1].Text = SkillDictionary.GetClassString(Class);
    }
    ListItem.SubItems[3].Text = DPS + ""; //DPS
    ListItem.SubItems[4].Text = String.Format("{0:#,0}", TotalDamage) + ""; //누적딜 23.07.20. 콤마 표시
    ListItem.SubItems[5].Text = TotalDealTime + "초"; //딜시간
    ListItem.SubItems[6].Text = PercentNormalCancel + "%"; //평캔
    ListItem.SubItems[7].Text = rd3 + ""; //새공비
    ListItem.SubItems[8].Text = percentCritical + "%"; //스킬치명
    ListItem.SubItems[9].Text = HealAmount + ""; //힐량
    ListItem.SubItems[10].Text = percentSkill + "%"; //스킬퍼센트
    ListItem.SubItems[11].Text = percentNormal + "%"; //평타퍼센트
    ListItem.SubItems[12].Text = MaxDamage + ""; //한방
    ListItem.SubItems[13].Text = NewTotalNormalDealCount + "회"; //평타공격
    ListItem.SubItems[14].Text = NewTotalSkillDealCount + "회"; //스킬공격
    ListItem.SubItems[15].Text = NewTotalDealCount + "회"; //새공격
    ListItem.SubItems[16].Text = TotalNormalCancelCount + "회"; //총평캔수
    ListItem.SubItems[17].Text = MaxNormalCancelCount + "회"; //최대평캔수
    ListItem.SubItems[18].Text = TotalDealCount + "회"; //공격수
    ListItem.SubItems[19].Text = (NewTotalNormalDealCount == 0) ? "0" : (NewTotalNormalDamage / NewTotalNormalDealCount) + ""; //평타평균
    ListItem.SubItems[20].Text = (NewTotalSkillDealCount == 0) ? "0" : (NewTotalSkillDamage / NewTotalSkillDealCount) + ""; //스킬평균
    ListItem.SubItems[21].Text = (NewTotalDealCount == 0) ? "0" : (TotalDamage / NewTotalDealCount) + ""; //평균딜
    ListItem.SubItems[22].Text = HPRecover + ""; //HP회복
    ListItem.SubItems[23].Text = MPRecover + ""; //MP회복

}

위처럼 만들어서

else
{
    user.AddRecover(e);
    this.BeginInvoke(new EventHandler(delegate (object s, EventArgs ee)
    {
        user.UpdataListView();
    }));
}

이런식으로 보여지게 되는거같습니다.
(처음부터 제가 만든게 아니라서…)

몇명의 정보는 아주 아무런 문제가 없는데
인원이 많아지고 정보량이 많아지면 실제로 1초의 정보를 처리하는데 10초 20초이상이 걸리더라구요.

이게 처리속도의 문제인지, 폼에서 보여지는 부분들이 새로고침되는 속도의 문제(?)인지
궁금하고, 개선의 여지가 있는지도 궁금합니다…!!

정렬은 사용하지 않고,(옵션으로 온오프가능)
폼에서 보여지는 라인은 실제로 2-300라인인데,
한 라인 한라인의 정보가 실시간으로 바뀌는 구조입니다.

초보인게 자랑은 아니지만… 질문도 아는 만큼 하게되는것같고…ㅠ_ㅠ
아무쪼록… 어떻게 개떡같이 말해도 찰떡같이 알아들어주셔서 어떤 방향으로 가닥을 잡을지…
혹은 아 이건 이래저래서 최선인거같다라던지…
도움을 주실 수 있는 분이 계시다면 감사히 겸허히 받아보겠습니다!!

2 Likes

뭐…저도 어디가 문제인지 잘 안보이긴하는데 하나 보이는 건,

문자열 처리 부분입니다.

보여주신 것처럼 이렇게 처리를 하게되면 매번 새로운 문자열이 생성되고, 그게 따라 가비지가 많이 필요하게 되는데, 저 Unit(단위) 부분을 다른 컨트롤로 빼서 지정을 하시고 ListItem에는 보통 단위가 포함되어 있지 않은 실제 값 Entity들이 들어가니까, 그렇게 바꿔보시면 어떨까요?

1 Like

댓글 감사드려요! 다이해는 못했지만

ListItem.SubItems[5].Text = TotalDealTime + “초”;

이부분을

ListItem.SubItems[5].Text = TotalDealTime;

이렇게 해보라는 의미 (의미만!!) 이신것 같은데 맞을가요…?

일단 바로 해보니, 인트형식을 스트링으로 변환할수없다고 하는걸보니
ListItem.SubItems[5].Text 여기에는 스트링값만 들어가나봅니다.
그렇다면… 뭔가… 큰변화가 필요할수 있겠군요
앞에뒤에 뭘 써넣는 정도로는 흠…

일단 말씀해주신 고견은 고이모셔두고 곱씹어 생각해보겠습니다!
(다음질문을 위해…)

감사합니다!!

2 Likes

네 타입 자체는 문자열 말고 숫자는 그냥 숫자타입 쓰시는 게 좋습니다.

사실 그렇게 해야 유지보수 측면에서 데이터 관리도 편하고…

그래서 바인딩 처리를 하는 게 간편한 것도 있습니다.

문자열 관련해서는

이것을 참고해보시면 좋을 것 같습니다.

일반적인 상황에서는 별로 신경 쓰지 않아도 되지만 1분에 100만건 이상의 문자열 변경이라면 문제가 되실 수 있는 부분입니다. (미세한 성능 딜레이의 누적효과)

3 Likes
  1. 상수를 연산에 많이 사용합니다.
    ListItem.SubItems[5].Text = TotalDealTime + "초"; //딜시간

“초” 와 같은 불변 값이 항상 연산에 사용됩니다.
그것도 성능이 가장 느린 string + string 연산이네요.

  1. Regex에 반복 값 사용이 많습니다.
    비슷한 문자열에 대한 매칭 작업이 반복되는 것은 당연히 성능 구멍이 될 것입니다.
    각 매칭을 항목 별로 나누시고, 시퀀스로 변경한 다음 조속한 반환을 유도하면 매칭 속도를 올릴 수 있을 것 같습니다.

그리고, 원하시는 성능을 얻기 위해서는 "파일 텍스트 필터기"를 만들어야 할 듯 보입니다.
유사한 프로젝트가 있는 지 검색해보고 벤치마크 점수가 제일 좋은 것을 적용해보시는 건 어떨까요?

2 Likes

화면을 실제로 갱신하는 부분을 최대한 덜 갱신할 수 있도록 해 보셔요. 그 부분이 가장 병목일 겁니다.

2 Likes

호옥시… 체크박스를 만들구… 체크했을 경우 화면갱신을 멈추는 방법? 같은게 있을가요 왠지 간단하지 않을까 싶은데… ^^;;;;

1 Like

감사합니다 참고할게요!! 고맙습니다!!

2 Likes

오… 파일텍스트필터기…!! 한번 검색해볼게요 답변 고맙습니다!!

1 Like

UI 가상화 키워드로
여기 커뮤니티에서 검색해 보세요

실제 뷰 포트(화면에 보여지는 부분) 만 랜더링 하고
보이지 않는 부분은 랜더링하지 않는 기법입니다.

3 Likes

10억 행 챌린지(The One Billion Row Challenge) - :outbox_tray: 정보 공유 - 닷넷데브 (dotnetdev.kr)

참고해보세요.

3 Likes

가상화를 찾다가 C#에서는 아직 못찾고 있는데
더블버퍼를 내용 이해를 잘못해서 적용했는데
깜빡임은 줄어서 외관상 보기는 더 좋아진…
혹시 WPF에서 얘기하는 가상화를 C#에서는 어떤 키워드로 검색을 해야할가요!!

======================================
글쓰고 바로 찾았습니다. 가상모드!!
한번 적용해보겠습니다.

======================================
저에겐 굉장히 힘든 코드이긴 한데 이 방법이 맞는것 같습니다.
감사드립니다!!

1 Like

개인 프로젝트라 코드를 공개할 수 있어서 깃허브에 올려 주실 수 있으면 아마도 여기 계신 분들이 최적화에 도움을 적극적으로 주시지 않을까 합니다.


한가지 팁을 드리자면 병목 현상을 구분해서 측정할 수 있습니다. 가령,

  • 문자열을 파싱하는 부분
    문자열을 파싱하는 부분을 몇가지 가짜 데이터로 1~2분 사이 100Mb 정도의 데이터가 되도록 한 후 확인
    → 제 생각에는 충분히 처리 가능한 데이터 같은데 다만 메인 스레드가 아닌 별도 스레드에서 처리해야 합니다.
  • 화면에 표시하는 부분
    가짜 데이터로 초당 몇회까지 가능한지를 확인하고 – (윈폼의 경우 초당 20~40회인가 그 즈음이 될 것입니다. 확인해보세요!) 그 이상의 데이터의 경우 어떻게 처리할지 고민
4 Likes

말씀만이라도 너무 감사드립니다. 디모이님 글을 보면서 참 인성이 고운분인게 느껴집니다…!!ㅎㅎ…

말씀해주신 구분하는 팁조차 저걸 어떻게 하지? 라는 수준이기때문에… 일단은 또 던져주신거 찾아보러 구글을 가보겟습니다!ㅋ

2 Likes

해결 내용 공유

기존 코드

private void StartWorker()
        {
            int SleepTime = (IsFile) ? 5 : 1;
            worker = new Thread
            (
                delegate ()
                {
                    lock (lockObject)
                    {
                        while (bRunning)
                        {
                            string line = streamReader.ReadLine();

                            if (!String.IsNullOrEmpty(line))
                            {
                                ParseLine(line.Trim());
                            }

                            // Oops yah, it was eating up cpu without this.
                            Thread.Sleep(SleepTime);
                        }
                    }
                }
            );
            worker.IsBackground = true;
            worker.Start();
        }

수정코드

private void StartWorker()
{
    int SleepTime = (IsFile) ? 1 : 1;      
    worker = new Thread
    (
        delegate ()
        {
            lock (lockObject)
            {
                while (bRunning)
                {
                    string line = streamReader.ReadLine();

                    if (!String.IsNullOrEmpty(line))
                    {
                        ParseLine(line.Trim());
                        // ##
                        MainForm.totalCount++;
                        if(MainForm.totalCount > 10000000)
                        { MainForm.totalCount = 0; }
                        //Console.WriteLine("Current Line = " + MainForm.totalCount + ", " + fileStream.Position + ", " + fileStream.Length);
                    }

                    if (fileStream.Position < fileStream.Length)
                    {
                        // Oops yah, it was eating up cpu without this.
                        if (MainForm.totalCount % 20 == 0)
                            Thread.Sleep(SleepTime);

                        //MainForm.instance.SetLabel("Fast Mode");
                        //Console.WriteLine("Fast Mode");
                    }
                    else
                    {
                        Thread.Sleep(SleepTime);
                        //Console.WriteLine("Slow Mode");
                        //MainForm.instance.SetLabel("Normal Mode");
                    }
                }
            }
        }
    );
    worker.IsBackground = true;
    worker.Start();
}

제가 직접 한게 아니기때문에… 설명은 못드리겠지만…
뭔가 기존에는 제한(?)같은게 걸려있었나봅니다…

더 해결 해야할 부분들이 많지만… 일단 공유드립니다.

4 Likes

진행상황을 공유해주시는 것은 작성자 본인에게도, 커뮤니티에게도 좋은 레퍼런스가 됩니다.

그런 의미에서 공유해주셔서 감사드립니다.

그런데…thread 통으로 critical section 으로 지정하시는 것은 정말 그렇게 하셔야만 하는지 검토가 필요해 보입니다.

물론 꼭 필요한 경우에는 lock을 획득하여 처리하는 것이 맞지만, 그것도 크리티컬 섹션을 짧게 가져가면서 부분적으로 성능적 이득을 취할 수 있습니다.

그런데 만약에 그 lock까지 걸려있는 스레드가 invoke를 통해 ui 동기화까지 얽혀있다면…병렬 컴퓨팅의 성능을 전혀 맛볼 수 없습니다.

보여지는 소스코드 상황상 다 추측은 어렵겠지만 한번 검토해보시는 것을 추천드립니다.

5 Likes

더이상 제가 초보라고 말씀드리기도 죄송할만큼
초보라는걸 마치 방패처럼 사용하는 느낌을 저도 느낀다면
보시는 분들은 더 할수도있겠다고 생각이 드네요.
아 쟤는 왜 자꾸 초보초보거리면서 해볼 생각을 안하는거야
라고…?
근데 진짜!! 후… 그렇습니다… 힘드네요 ㅠ_ㅠ
일단 코드를 저렇게 바꾸어주신분께 한번 여쭤는 보겠습니다.
더 나은 방향을 제시해주셔서 감사드립니다!!

1 Like

아니요. 초보를 방패처럼 사용하신다고 생각하지는 않습니다.

저도 사실 지금도 초보이고, 경험자들이 코드를 이렇게 바꿔봐라 저렇게 바꿔봐라 할 때 저도 망설입니다.

관련이 없겠지만 예시로 지금 오픈소스들을 fork받아서 제 마음대로 바꿔보고 있는데, 저도 이렇게 해볼까 저렇게 해볼까만 해보고 실제로 하기전에 망설이는 것들이 있습니다.

스스로 가만히 생각해보면 이것은

내가 만든 것이 아닌데, 다른 사람이 만든 구조를 헤쳐서 동작하지 않으면 어쩌지?

라는 공포에서 오는 것 같습니다.

질문자님도 마찬가지이실 것 같아요.

그러니까 백업해두시고, 또는 저장소 Clone 하신거라면 Branch 새로 따셔서 시도해보시고

자료 서칭을 하신게 있으면 이것저것 만져서 동작이라도 되는지 때려 맞춰보시면 조금 괜찮아 질 거 같습니다.

사실 이런 (내가 만든게 아닌 것을 다루는) 무지에서 오는 공포를 이겨내려면, 그래서 테스트 코드를 단단하게 다져가야하는데 사실 매번 그렇게 하기가 경력자도 쉽지는 않습니다.

시도해보시고 레퍼런스를 자신을 위해서라도 포럼에 남겨주시면 좋을 것 같습니다.

1 Like

넵넵!
백업하고 바꿔보는건 전혀 두렵지 않습니다!!
문제는 무슨 소리인지 모른다는거에요…ㅠ_ㅠ

말씀해주신 내용을 토대로 빠른 구글링을 해본결과…
제가 이해하고 있는 상황은!

제프로그램은 실시간으로 한줄씩 (많은경우 초당 500줄 적으면 1~20줄) 계속 라인이 추가되는
단일 파일을 항상 액세스하여 라인이 추가되면 해당 라인을 해석하는 프로그램입니다.
(그런 여러 이유때문에 이렇게 만든게 아닐까… 생각해봅니다…)

아직 저는 스레드가 무슨말인지, (컴퓨터업종이라 뜻은 알지만 언어적 차원에서는 무지한…)
모르는 정도여서 ㅠ_ㅠ

이렇게 길게 정성껏 댓글을 남겨주시구, 그 마음이 어떤 마음인지는 저도 공감합니다!
좀더 공부하고 노력해서 제 자신과 다른 분들에게도 도움이 되는게 제 희망입니다!

써주신 내용들 빠트림 없이 검색해보고 공부해보겠습니다
거듭 감사드립니다! 꾸벅

1 Like