foreach 구문 처리 속도가 점점 느려집니다.

@Vincent dipose라는 메서드가 관리되지 않는 리소스 정리 하는 게 있다는데… 일단 그거 한번 적용 시켜보려고 합니다.

2개의 좋아요

아…그거는 아마 연관이 없을 것 같습니다.
Dispose를 한다고해도 바로 제거되는 게 아니라서요.
게다가 IDisposable 인터페이스를 구현하지 않았다면 Dispose에 기본코드만 들어있을 수도 있습니다.

2개의 좋아요

아 그런가요… 안타깝네요… 허허…

2개의 좋아요

작업 앞뒤로 table.BeginLoadData();table.EndLoadData();를 넣어서 불필요한 추가 작업이 발생되지 않도록 구성해도 동일한걸까요?

var table = new DataTable();
table.BeginLoadData();
Enumerable.Range(1, 500)
    .ToList().ForEach(x =>
    {
        table.Columns.Add(new DataColumn("Column" + x));
    });
table.EndLoadData();
3개의 좋아요

참고해서 한번 진행해보겠습니다.

조언 감사합니다.

@nyjin 시도해봤는데 현상 동일하게 발생하네요… ㅠㅠ

2개의 좋아요

좋은 글이 있었네요~

4개의 좋아요

DataTable에 데이터를 담는 부분의 전체 소스가 있었다면… 하는 아쉬움이 드네요

4개의 좋아요
string[] lines = System.IO.File.ReadAllLines(opf.FileName);
            if (lines.Length > 0)
            {
                dt.Clear();

                //first line to create header
                string firstLine = lines[0];
                string[] headerLabels = firstLine.Split(',');
                
                foreach (string headerWord in headerLabels)
                {
                    dt.Columns.Add(new DataColumn(headerWord));


                }

이렇게 들어가고 .csv 파일에 있는 데이터를 가져오는 소스 입니다.

이 이외에 따로 데이터를 담기 위한 소스는 없네요.

2개의 좋아요

DataTable 로 사용하는 이유가 따로 있으신가요?

직접 순수 VO 클래스를 만들어서 사용하는걸 권장 드립니다.

데이터그리드 등으로 UI에 데이터 표시 목적으로 DataTable을 사용하시는 거라면

VO 모델 클래스로 바인딩도 가능합니다.

DataTable은 무겁고 또한 많은 컬럼을 추가 하기 위해 new DataColumn 처리는
내부적으로 해당 데이터타입을 위해 리플렉션을 동반합니다.
때문에 속도가 느리고 메모리 효율성도 딱히 좋다고 볼 순 없으며 느릴수 밖에 없습니다.

왠만하면 클래스 바인딩 방식으로 추천 드립니다.

3개의 좋아요

공유해주신 코드는 런타임 상에서 여러번 불리는걸까요?

string[] lines = System.IO.File.ReadAllLines(opf.FileName);
if (lines.Length > 0)
{
    dt.Clear();

    //first line to create header
    string firstLine = lines[0];
    string[] headerLabels = firstLine.Split(',');
    
    foreach (string headerWord in headerLabels)
    {
        dt.Columns.Add(new DataColumn(headerWord));


    }
}
2개의 좋아요

아직도 문제가 해결되지 않으셨으면, 깃허브 등을 통해 재현 가능한 소스코드를 공유해주시면 아마도 이곳 여러 분들이 도움을 드릴 수 있을 것 같습니다.

2개의 좋아요

사용하는 이유는 따로 없습니다.

제가 지식이 얕다보니 구글링하면서 사용할 수 있는 부분을 참고해서 코드를 짜다 보니 사용하게되었습니다.
VO 모델 클래스… 한번 구글링 해보겠습니다.

조언 감사합니다.

2개의 좋아요

프로그램 내에서 처음 .csv 파일을 불러왔을 때 바로 사용이 되고, 그 이후에 시간에 따른 조회나 특정 컬럼에 있는 데이터들을 모아주거나 하는 등등 여러 번 사용되는 코드 입니다.

특정 액션을 취할 때 번복해서 사용하는 것은 아닙니다.


가입 초기라 하루에 달 수 있는 댓글 수 제한이 있어 부득이하게 달았던 댓글에 편집을 이용해서 답니다…ㅠㅠ

@nyjin 그러면 하나 여쭙고 싶은 게 있는데

현재는 전역 메서드라고 하나요…? DataTable dt = new DataTable()을 각 메서드 내에서가 아닌 class 내에서 언급을 해주고 그때그때 Clear()를 사용해서 안에 내용을 초기화 해주고 사용을 하고 있었는데,

class에서 언급한 dt 외에 각 메서드에서 DataTable을 생성하여 그 내용을 그대로 class 단위의 DataTable에 넣을 경우에 위와 같은 GC 영향을 안받을 수 있을까요…??

2개의 좋아요

아… 제가 깃허브를 사용을 안해봐서 잘 모르기도 하고, 제 개인 개발을 하고 있지만 회사에 종속된 코드다 보니 그렇게 해도 괜찮을지 모르겠네요 ㅠㅠ…


@dimohy 아 그렇군요…

친절한 답변 및 예시 확인 감사합니다.
dataset을 사용해보려고 생각 중이긴 한데… 안되면 모든 메서드에 대해서 새로운 DataTable을 사용을 하는 방식으로 다시 고민을 해봐야겠네요.

2개의 좋아요

Clear 함수가 DataTableRow만을 삭제할 뿐 Column들은 그대로 두기 때문에 반복 호출할 경우 컬럼 리스트가 누적 증가하여 메모리 누수로 이어질 가능성이 있습니다. (디버깅 해보시거나, 메모리 변화를 확인해 보세요.)
메모리 사용이 일정 수준으로 넘어가게 되면 @Vincent 님 말씀처럼 GC의 호출 빈도가 잦아지고, DataTable에 연결된 Column들은 사용 중인 메모리로 판정되어 삭제되지 않게 되는데요.

DataTable을 매번 초기화해서 사용하는 서비스 시나리오라면, DataTable을 새로 생성해서 사용하시는게 오히려 도움이 될 수 있을 것 같습니다.

6개의 좋아요

.NET Framework 4.7.2에서 간단하게 테스트 코드를 작성한 후 증상을 확인하였습니다.

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var sw = Stopwatch.StartNew();
            DataTable dt = null;
            for (var i = 0; i < 1000; i++)
            {
                dt = MakeTempDataTable(dt);
                Console.WriteLine(sw.Elapsed);
                sw.Restart();
            }
        }

        static DataTable MakeTempDataTable(DataTable dt)
        {
            if (dt is null)
                dt = new DataTable("Temp Table");
            else
            {
                //dt.Rows.Clear();
                //dt.Columns.Clear();

                dt = new DataTable("Temp Table");
            }

            for (var columns = 0; columns < 1000; columns++)
                dt.Columns.Add(new DataColumn($"{columns}1"));

            for (var row = 0; row < 10000; row++)
            {
                var newRow = dt.NewRow();

                for (var columns = 0; columns < 1000; columns++)
                {
                    newRow[columns] = columns;
                }
            }

            return dt;
        }
    }
}

dt.Rows.Clear()dt.Columns.Clear()를 하더라도 DataTable 인스턴스가 가지고 있는 메모리 자원이 GC이후에도 해제되지 않고 계속 증가하는 것을 확인할 수 있었습니다.

DataTable 인스턴스를 새로 만들어서 사용했을 경우 그런 문제가 없었습니다.

결론은, @nyjin님이 제안한 것처럼 그냥 DataTable의 인스턴스를 새로 만들어서 사용해보세요.

8개의 좋아요

코드 구조를 보지 않고 말씀만으로 이슈를 해결하는 것은 분명한 한계가 있기 때문에, 회사 코드라 공유가 어렵다면 모사 코드라도 구성하여 공유해주셔야 이슈도 빠르게 해결되고, 좀더 진전된 대화가 오갈 수 있지 않을까 싶네요.

공유해주신 코드에서 DataTableColumn 추가하는 코드가 실행 된다는 것은 DataTable을 새로 구성할 가능성이 높아 이미 사용중인 DataTable을 폐기하고 새로 구성하여 GC의 메모리 정리 부담을 줄여주는게 좋을 것 같아 dt.Clear() 대신 새로 인스턴스를 구성하는게 좋겠다는 의견을 드렸습니다.

5개의 좋아요

일단 텍스트 파일에서 불러오는 부분을 랜덤값으로 대체하고 다른 부분은 거의 동일하게 테스트 해봤고요…

올려주신 소스에서는 DataTable 정의가 안 보여서, 저는 외부에 선언해놓고 시도해봤습니다.

.NET Framework4.7.2 에서 500개씩 반복적으로 추가했으며, 그때마다 시간을 측정했습니다. (단위: ms)

image

이렇게만 봐서는 Column만 추가하는 건 속도에 영향을 거의 주지 않는 것처럼 보입니다.

소스는 아래와 같이 했습니다.


        DataTable dt = new DataTable();
        private void button1_Click(object sender, EventArgs e)
        {
            DateTime dtBegin = DateTime.Now;

            StringBuilder strTest = new StringBuilder();
            for (int i = 0; i < 500; i++)
            {
                Random rnd = new Random(Guid.NewGuid().GetHashCode());
                if (strTest.Length == 0)
                {
                    strTest.Append("t" + rnd.Next());
                }
                else
                {
                    strTest.Append(",t" + rnd.Next());
                }
            }

            //first line to create header
            string firstLine = strTest.ToString();
            string[] headerLabels = firstLine.Split(',');

            foreach (string headerWord in headerLabels)
            {
                try
                {
                    dt.Columns.Add(new DataColumn(headerWord));
                }
                catch
                {
                    continue;
                }
            }


            textBox1.AppendText($"{dt.Columns.Count} : {(DateTime.Now - dtBegin).TotalMilliseconds} ms" + Environment.NewLine);
        }

5개의 좋아요

아 넵넵 지난번에 달아주셨던 댓글 보고 어느 정도 이해가 되었습니다.

아무래도 메모리가 계속해서 누적되는 문제로 보여 좀 번거롭지만 DataTable을 각각의 메서드에서 언급하여 새로이 데이터를 넣어주려고 합니다.

추후에는 가능하면 코드도 공유가 될 수 있도록 해보겠습니다.

감사합니다.

4개의 좋아요

코드까지 짜서 보여주시는 열정에 감사드립니다.

제가 이 코드를 보고 한번에 이해를 하면 좋겠지만… 그러지 못한 실력에 안타까울 뿐이네요…

2개의 좋아요