숫자를 문자열로 변환할 때 ToString 메서드를 이용합니다.

 

숫자를 문자열로 자주 변경할시에 아래와 같이 LookupTable을 이용하여 구현하시면

 

빠른 속도를 얻으실 수 있습니다.

 

 

 

 

 

 

 

테스트 코드입니다.

 

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Diagnostics;

 

namespace ToStringOptimization

{

    class Program

    {

        static void Main(string[] args)

        {

            //클래스 메모리 미리 올려두기

            ToStringExtensions.ToStringLookup(1);

 

            Stopwatch st = Stopwatch.StartNew();

            for (int i = 0; i < 256; i++)

            {

                string s = i.ToString();

            }

            st.Stop();

            Console.WriteLine("ToString : {0}", st.Elapsed.ToString());

 

            st = Stopwatch.StartNew();

            for (int i = 0; i < 256; i++)

            {

                string s = i.ToStringLookup();

            }

            st.Stop();

            Console.WriteLine("ToStringLookup : {0}", st.Elapsed.ToString());

 

            Console.ReadKey();

        }

    }

} 

 

테스트 결과 :

 

 

 

 

테스트 프로젝트 : ToStringOptimization.zip

 

회사에는 FarPoint라는 이쁘고 쉽게 구현 가능한 컴퍼넌트가 있습니다. 제가 잘 사용 못하는건지 맨 처음 메모리에 올릴 때 화면이 늦게 떠서 이번 프로젝트에는 성능 좋은 DataGridView를 이용하였습니다. Column 7개에 30~500개 되는 Row를 추가해 주어야 하였습니다.

 

(바인딩을 이용하진 않았습니다. 직접 Colume, Row에 Index로 접근하여 데이터를 삽입하는 것도 같은 성능 개선 효과를 내기 때문에 바인딩에 신경 안쓰셔도 됩니다.)

 

아래와 같이 DataGridView의 AutoSizeColumnsMode를 AllCells로 지정하고 바인딩하였습니다.

Column 크기를 제가 직접 작성하기 귀찮아서 AutoSize모드를 설정하였었습니다.

그런데 이상하게 너무 느린겁니다. 30개 정도 DataGridView에 넣는데는 상관없지만 500개이상되면 5초 이상 걸리게 되었습니다.

/// <summary>

        /// 좋지 않은 예제

        /// </summary>

        /// <param name="dt"></param>

        private void StartBadExample(DataTable dt)

        {

            //AutoSizeColumnMode None 아닌것으로 설정

            this.dgv_test.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;

 

            this.dgv_test.SuspendLayout();

 

            System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();

            st.Start();

            this.dgv_test.DataSource = dt;

            st.Stop();

            System.Diagnostics.Debug.WriteLine(st.Elapsed.ToString());

 

            this.dgv_test.ResumeLayout();

        }

 

 

그래서 이것저것 찾아본 결과 답을 얻게 되었습니다.

코드 내용은 아래와 같습니다.

/// <summary>

        /// 좋은 예제

        /// </summary>

        /// <param name="dt"></param>

        private void StartGoodExample(DataTable dt)

        {

            this.dgv_test.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None;

 

            this.dgv_test.SuspendLayout();

           

            System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();

            st.Start();

            this.dgv_test.DataSource = dt;

            st.Stop();

            System.Diagnostics.Debug.WriteLine(st.Elapsed.ToString());

 

            //AutoSizeColumnsMode 변경

            this.dgv_test.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

 

 

            //TopLeftHeaderCell 또는 RowHeader 이용한다면 변경

            this.dgv_test.RowHeadersWidth = 100;

 

            this.dgv_test.ResumeLayout();

        }

 

 

데이터 삽입 전 AutoSizeColumnsMode를 DataGridViewAutoSizeColumnsMode.None으로 설정 하시고, 데이터를 모두 삽입한 후 AutoSizeColumnsMode를 원하시는 것으로 변경해주시면 되겠습니다.

 

DataGridView의 Cell 그리는 로직은 AutoSizeColumnsMode의 값이 None이 아닐 경우에는 이전 그려진 Cell들을 다시 그리느냐고 느려지는 것 같습니다.

 

Column 10개, Row 300개로 테스트 한 결과는 아래와 같이 엄청난 차이를 보입니다.

StartBadExample의 시간: 00:00:00.3024992

StartGoodExample의 시간: 00:00:00.0000006

Winform의 ListView를 사용하다보면 한개 이상의 ListViewItem을 추가할때가 있습니다.

그럴때 BeginUpdate와 EndUpdate를 이용하면 깜빡임을 최소화하고 아이템들이 추가되는 시간을 줄 일 수있습니다.

 

ListView의 Item 추가하는 로직은 잘 모르겠지만 제 생각에는 아래와 같은 구조일 것 같습니다.

 

 

         

/// <summary>

        /// 기존 System.Windows.Forms.ListViewItem 컬렉션에 추가합니다.

        /// </summary>

        /// <param name="?"></param>

        public void Add(ListViewItem value)

        {

            //Dowork..

 

            //BeginUpdate Count 0 아니라면 컨트롤을 다시 그립니다.

            if(this.beginUpdateCount!=0)

                this.Invalidate();

        }

 

 

MSDN에 ListVIew BeginUpdate에 대한 설명에는 아래와 같이 나와있습니다.

 

BeginUpdate 를 두 번 이상 호출하는 경우 EndUpdate를 같은 횟수만큼 호출해야 합니다. EndUpdate 를 마지막으로 호출한 후에 ListView가 다시 그려집니다.이렇게 하지 않으면 BeginUpdate를 여러 번 호출해도 효과가 없습니다.

참고 : http://msdn.microsoft.com/ko-kr/library/60dc8hf5(v=vs.100).aspx

 

즉, BeginUpdate를 호출하면 ListView 컨트롤을 다시 그리지 않습니다.

아이템을 추가하기 전에 BeginUpdate 메서드를 호출하여 ListView를 다시 그리지 않게 막은 후

아이템을 모두 추가한 후에 EndUpdate 메서드를 호출하여 ListView를 다시그려주게되면 성능이 향상하게 됩니다.

 

아래의 테스트 결과 이미지를 확인해보시면 그냥 Item을 추가한 경우와  BeginUpdate, EndUpdate 메서드를 호출할 때의 성능 차이를 확인 하실수 있습니다.

약 2배의 성능이 향상 되었습니다. 직접 테스트를 진행해보시면 깜빡임도 줄어든것을 확인 하실 수 있습니다.

(ListViewItem 500개 추가할때의 테스트)

 

[이미지 1] 테스트 결과

 

 

 

 

 

 

 

 

 

코드 :

 

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Diagnostics;

 

namespace TestPerformance

{

    public partial class TestListViewPerformance : Form,ITest

    {

        const string TXT_NOT_USED_UPDATE = "Not call Update mehthod : {0}";

        const string TXT_USED_UPDATE = "Call Update mehthod : {0}";

 

        public TestListViewPerformance()

        {

            InitializeComponent();

        }

 

        /// <summary>

        /// ListViewItem 추가합니다.

        /// </summary>

        /// <param name="isUseUpdateMethod">BeginUpdate, EndUpdate 호출 여부</param>

        /// <param name="lvis">ListViewItem 배열</param>

        private void AddListViewItem(bool isUseUpdateMethod, ListViewItem[] lvis)

        {

            if (isUseUpdateMethod)

            {

                this.lv_test.BeginUpdate();  

            }

 

           

foreach (var item in lvis)

            {

                this.lv_test.Items.Add(item);

            }

            // AddRange 내부적으로 BeginUpdate EndUpdate 호출하는 같습니다.

            //this.lv_test.Items.AddRange(lvis);

 

            if (isUseUpdateMethod)

            {

                this.lv_test.EndUpdate();

            }

        }

       

 

        /// <summary>

        /// BeginUpdate, EndUpdate 사용하지 않는 로직을 수행합니다.

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void btn_notUseUpdate_Click(object sender, EventArgs e)

        {

            Stopwatch sw = new Stopwatch();

 

            this.lv_test.Items.Clear();

            sw.Start();

 

            this.AddListViewItem(false, this.CreateTestListViewItems());

 

            sw.Stop();

           

            Console.WriteLine(string.Format(TXT_NOT_USED_UPDATE,sw.Elapsed.ToString()));

            Debug.WriteLine(string.Format(TXT_NOT_USED_UPDATE, sw.Elapsed.ToString()));

        }

 

        /// <summary>

        /// BeginUpdate, EndUpdate 사용하는 로직을 수행합니다.

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void btn_useUpdate_Click(object sender, EventArgs e)

        {

            Stopwatch sw = new Stopwatch();

 

            this.lv_test.Items.Clear();

            sw.Start();

 

            this.AddListViewItem(true, this.CreateTestListViewItems());

 

            sw.Stop();

 

            Console.WriteLine(string.Format(TXT_USED_UPDATE, sw.Elapsed.ToString()));

            Debug.WriteLine(string.Format(TXT_USED_UPDATE, sw.Elapsed.ToString()));

        }

 

        /// <summary>

        /// Test ListViewItem 생성합니다.

        /// </summary>

        /// <returns></returns>

        private ListViewItem[] CreateTestListViewItems()

        {

            ListViewItem[] lvis = new ListViewItem[500];

 

            for (int i = 0; i < lvis.Length; i++)

            {

                lvis[i] = new ListViewItem(i.ToString());

                for (int j = 1; j < this.lv_test.Columns.Count; j++)

                {

                    lvis[i].SubItems.Add(i.ToString());

                }

            }

 

            return lvis;

        }

 

        public void StartTest()

        {

            throw new NotImplementedException();

        }

    }

}

  

 

C#의 Winform에서 Form을 생성해보면

*.cs와 *.Designer.cs가 생성됩니다.

 

partial Class로써 디자인 부분과 개발 코드가 분리 되어 있습니다.

 

디자인의 초기화 하는 메서드를 확인해보면 아래와 같이

붉은 부분의 코드가 작성되어 있는것을 확인 하실 수 있습니다.

 

        /// <summary>
        /// 디자이너 지원에 필요한 메서드입니다.
        /// 이 메서드의 내용을 코드 편집기로 수정하지 마십시오.
        /// </summary>
        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(678, 416);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);

        }


 

 

SuspendLayout과 ResumeLayout이 뭔지 살펴보겠습니다.

둘다 Control Class의 메서드로써 MSDN을 살펴보면 아래와 같습니다.

 

 

 SuspendLayout 메서드는 컨트롤의 Layout 논리를 임시로 일시 중단합니다.

ResumeLayout 메서드는 일반 레이아웃 논리를 다시 시작합니다.

 

 

 ResumeLayout 메서드가 호출될 때까지 컨트롤의 레이아웃 논리가 일시 중단됩니다.

SuspendLayout ResumeLayout 메서드는 차례로 사용되어 컨트롤의 여러 특성을 조정하는 동안 여러 Layout 이벤트가 발생하지 않도록 합니다.

예를 들어 SuspendLayout 메서드를 호출하고 컨트롤의 Size, Location, Anchor 또는 Dock 속성을 설정한 다음 ResumeLayout 메서드를 호출하면 변경 사항이 적용됩니다.

ResumeLayout 을 성공적으로 호출하려면 SuspendLayout에 대해 보류 중인 호출이 없어야 합니다.

 

주목! 

 하나의 부모 컨트롤에 여러 개의 컨트롤을 추가할 경우 추가할 컨트롤을 초기화하기 전에 SuspendLayout 메서드를 호출하는 것이 좋습니다.

부모 컨트롤에 컨트롤을 추가한 후 ResumeLayout 메서드를 호출합니다.이렇게 하면 컨트롤이 많은 응용 프로그램의 성능이 향상됩니다.

 

 SuspendLayout MSDN 바로가기

ResumeLayout MSDN 바로가기

 

 

 

아래의 코드로 테스트를 진행해 보았습니다.

 

 

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace TestLayoutPerformance
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();

            
            int testCount = 10;
            
            stopWatch.Start();
            this.UseLayout(testCount);
            stopWatch.Stop();
            System.Diagnostics.Debug.WriteLine(string.Format("Not raised layout event time:{0}",stopWatch.Elapsed.ToString()));

            this.Controls.Clear();


            stopWatch.Restart();

   this.NotUseLayout(testCount); stopWatch.Stop(); System.Diagnostics.Debug.WriteLine(string.Format("Raised layout event time:{0}", stopWatch.Elapsed.ToString())); } /// <summary> /// Layout 이벤트를 발생시키지 않습니다. /// </summary> /// <param name="testCount"></param> private void NotUseLayout(int testCount) { Random rand = new Random(); for (int i = 0; i < testCount; i++) { Button btn = new Button(); btn.Text = i.ToString(); btn.Location = new Point(rand.Next(0, this.Width), rand.Next(0, this.Height)); this.Controls.Add(btn); } } /// <summary> /// Layout 이벤트를 발생시킵니다. /// </summary> /// <param name="testCount"></param> private void UseLayout(int testCount) { Random rand = new Random(); this.SuspendLayout(); for (int i = 0; i < testCount; i++) { Button btn = new Button(); btn.Text = i.ToString(); btn.Location = new Point(rand.Next(0, this.Width), rand.Next(0, this.Height)); this.Controls.Add(btn); } this.ResumeLayout(false); } } }

 

 

 

 

테스트 결과를 확인해보면..

많은 개수의 Control 개체를 추가할때 속도 개선에 도움을 줍니다..

(SuspendLayout 메서드를 호출하게되면 Layout 이벤트는 발생하지 않으므로..)

 

500개 Control 생성시 결과:
Not raised layout event time:00:00:00.0212146
Raised layout event time:00:00:00.0286526

 

1000개 Control 생성시 결과:
Not raised layout event time:00:00:00.0736165

Raised layout event time:00:00:00.1003722

 

5000개 Control 생성시 결과:
Not raised layout event time:00:00:01.6119380

Raised layout event time:00:00:02.3460958

 

 

10000개 Control 생성시 결과:
Not raised layout event time:00:00:06.6405698

Raised layout event time:00:00:09.6261999



 

 

 

 

 

TestLayoutPerformance.zip

+ Recent posts