Poolmon에서의 Thre Tag 관련 (CreateThread? AfxBeginThread!)

PoolMon 시스템의 paged 그리고, nonpaged kernel pools 부터의 메모리 할당등을 모니터링해주는 Tool인데, 아래와 같은 Tag 표시된 형태로 리포트를 출력하는 툴이다.

 

Tag  Type     Allocs         Frees    Diff   Bytes    Per Alloc

. . .

 TcFp Nonp          1         0         1     520        520       

 TcpB Nonp          1         0         1     520        520       

 Thre Nonp    2294834   1932203    362631 226281744        624

 

그러므로, Tag 의미를 알아차리는 것이 중요한데, 상위처럼, Thre 라는 Tag Thread Objects 의미하는 것이며, Nonp Nonpaged Pool 메모리를 의미한다.  복잡한 kernel을 고민하는 것 외에 일감은 Multiple Threaded Application에서 Worker threads 생성/소멸과 관련하여 누수가 있는 여부를 Check하는 것이 필요할 수도 있다. 예를 들어, 간혹, 아래와 같은 코드가 존재할 수도 있다

 

void xxxProcedure(SOCKET s) {

             CreateThread(NULL, 0, tcpxxThreadProc, (LPVOID)s, 0, NULL);
}

 

해당 code tcpxxTreadProc 이라는 Callback function 정상적으로 Close 되면 문제되지 않을 지도 모른다고 생각할 수도 있다. 하지만, 그렇지 않다. CreateThread 명세(http://msdn.microsoft.com/en-us/library/ms682453(VS.85).aspx) 보면, 다음의 언급이 존재한다.

 " ... The thread object remains in the system until the thread has terminated and all handles to it have been closed through a call to CloseHandle. "

 

그러므로, 핸들 누수가 있다는 언급이다. 그러므로, 다음과 같이 Code 수정될 필요도 있어 보인다.

 

void xxxProcedure(SOCKET s)

{

    HANDLE hThread = CreateThread(NULL, 0, tcpxxThreadProc, (LPVOID)s, 0, NULL);

    CloseHandle(hThread);

}

 

여기서 한가지 언급할 부분은 MFC 애플리케이션이라면 CreateThread 사용을 권하지 않는 . 보다는 AfxBeginThread 이용하여 변경하는 것이 올바른 접근으로 보인다. (참조: http://msdn.microsoft.com/en-us/library/69644x60(VS.80).aspx)

 

UINT tcpxxThreadProc( LPVOID pParam )

{

    // {....}
}

void xxxProcedure(SOCKET s)

{

    AfxBeginThread(_tcpxxThreadProc, NULL) ;

}

 

by 강세윤 | 2010/02/04 13:24 | Windows debugging | 트랙백 | 덧글(0)
Cannot close undo unit because no opened unit exists

XP SP2환경의 WPF Application은 다음과 같은 문제로 인한 Crash 현상을 경험할 수 있다.

 Cannot close undo unit because no opened unit exists.


만일, 영문 OS에서 한글IME를 올려 사용한다면 가능성이 충분하다. 다음의 코드는 WPF Application으로 TextBox에 입력 String의 제한을 위해 OnTextChange Event Override 하였으며, Binding ValidationRule을 추가하여 문자열의 숫자입력을 제한하고자 하는 경우에 생각해 볼 수 있는 단순한 프로그램이다. 문제는 Data Binding TextBox OnTextChange Event안에서 Directly하게 Text Property Data Assign 하는 경우에 “Cannot close undo unit…” 이라는 Exception과 함께 Application Crash가 발생한다.

 

 <Window x:Class="ImeTest.Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:ImeTest"

    Title="Window1" Height="300" Width="300">

    <StackPanel>

        <local:MaxLengthTextBox x:Name="TextBox1" NewMaxLength="10" Height="87" >

            <TextBox.Text>

                <Binding Path="Text" ElementName="TextBox2" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True" >

                    <Binding.ValidationRules>

                        <local:ValidateString></local:ValidateString>

                    </Binding.ValidationRules>

                </Binding>

            </TextBox.Text>

        </local:MaxLengthTextBox>

        <TextBox Name="TextBox2" Height="86" Background="LightBlue">

        </TextBox>

    </StackPanel>

   

</Window>

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

 

namespace ImeTest

{

    /// <summary>

    /// Interaction logic for Window1.xaml

    /// </summary>

    ///

   

    public partial class Window1 : Window

    {

        public Window1()

        {

            InitializeComponent();

         }

    }

 

    public class ValidateString : ValidationRule

    {

        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)

        {

            string obj = value as string;

            foreach (char c in obj.ToString())

            {

                if (char.IsNumber(c))

                {

                    return new ValidationResult(false, "Only Characters!!");

                }

            }

            return ValidationResult.ValidResult;

        }

    }

 

    public class MaxLengthTextBox : TextBox

    {

        public static readonly DependencyProperty NewMaxLengthProperty = DependencyProperty.Register("NewMaxLength", typeof(int), typeof(MaxLengthTextBox),

            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnNewMaxLengthChanged)), new ValidateValueCallback(ValidateNewMaxLength));

 

       

        public int NewMaxLength

        {

            get { return (int)GetValue(NewMaxLengthProperty); }

            set { SetValue(NewMaxLengthProperty, value); }

        }

 

        public MaxLengthTextBox()

        {

 

        }

 

        protected override void OnTextChanged(TextChangedEventArgs e)

        {

            base.OnTextChanged(e);

            if (NewMaxLength < Text.Length)

            {

                int overflowCount = Text.Length - NewMaxLength;

                if (overflowCount > 0)

                {
                  
TextChange change = e.Changes.First<TextChange>();

                   

                    Text = Text.Substring(0, change.Offset) +

                            Text.Substring(change.Offset, change.AddedLength - overflowCount) +

                            Text.Substring(change.Offset + change.AddedLength);

 

                    CaretIndex = NewMaxLength;

                }

            }

        }

 

        private static void OnNewMaxLengthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {

            TextBox textBox = (TextBox)d;

            int newMaxLength = (int)e.NewValue;

 

            if (textBox.Text.Length > newMaxLength)

            {

                textBox.Text = textBox.Text.Substring(0, newMaxLength);

            }

        }

 

        private static bool ValidateNewMaxLength(object value)

        {

            return (int)value >= 0;

        }

    }

}

 

한가지 추가 되야 하는 조건은 항상 이 문제가 발생하는 것은 아니고, IME 설정에서 “Turn Off Advanced Text Services” Check 되어 있거나, IME“Korean Input System(MS-IME 2002)” 와 같은 상태의 경우에만 해당 이슈를 경험할 수 있다.

 

이러한 경우에는 OnTextChanged Event에서 Text Property에 직접 Assign 하는 대신에 다음과 같은 코드를 추가함으로써 문제를 피해갈 수 있다.

 

         //Text = Text.Substring(0, change.Offset) +

         //        Text.Substring(change.Offset, change.AddedLength - overflowCount) +

         //        Text.Substring(change.Offset + change.AddedLength);

         using (this.DeclareChangeBlock())

         {

             this.Clear();

             this.AppendText(Text.Substring(0, change.Offset) +

                          Text.Substring(change.Offset, change.AddedLength - overflowCount) +

                          Text.Substring(change.Offset + change.AddedLength));

         }

 
+) 상위code는 http://social.msdn.microsoft.com/forums/en-US/wpf/thread/f1a347db-2232-4beb-90c4-92e5c0ee995c 를 참조하였으며 문제 재현을 위해 일부를 변경하였다.

by 강세윤 | 2010/02/02 16:08 | 정리되지 않은 기술 | 트랙백 | 덧글(0)
Korean LCID에 대한 Sort Order 문제

Windows에서 한글을 사용하여 프로그래밍하는 것은 쉽지 않은 문제들이 존재한다. DBCS라던지 Unicode 라던지 이것 저것 고려할 것이 많은 것이 사실이다. 그래서 그런지 아래와 같은 이슈를 만난다는 것은 그리 낯설은 일은 아닌 하다.

 

                  LCID LocaleLCID = 0x412 ; // ko-KR

                  int iRet = CompareStringW(LocaleLCID, 0, _T("A"), -1, _T(""), -1 );

 

상위의 code iRet 값이 1,2,3이냐에 따라서 A>, A=, A< 임을 보여주는 , 이것이 Windows Vista에서는 과거 OS와의 호환성 이슈가 발생한다. 일단, Windows XP/2003에서는 iRet값이 3이나오는 반면에 Windows Vista에서는 1 나온다. 부분에 대해서 Michael Kaplan (Microsoft에서 International language Localization 관련한 개발업무의 Program Manager) 블로그(http://blogs.msdn.com/michkap/archive/2004/12/31/344888.aspx, " Sorting it all Out : Unlike LCMapString, the sort keys for English characters precede the sort keys for Korean ") 보면 재미있는 사실이 언급되어 있다. 이것은 Sort Key 관련이 있다.

 

Well, a decision was made back in the early days of Windows (that incidentally many have had cause to regret) to cause ideographs for Korean to be sorted in front of all of the other letters (including the Latin script letters of English). [...]

 

It is worth mentioning that text in the SortKey topic is a little confusing since it does not make clear that this only happens for the Korean LCID. And since the Windows behavior is not completely documented it does not cover the fact that neither Extension A nor Extension B ideographs are supported by it (though at present none of them are given intentional Korean-specific weight, a fact that will change in future versions).

 

그러므로, "... cause ideographs for Korean to be sorted in front of all of the other letters (including the Latin script letters of English..." 이와 같은 규칙이 Windows XP Windows 2003에서 Korean LCID에서만 발생한다는 것이 대단(?)하다. 하지만, 아쉽게도 이러한 규칙을 Windows Vista에서는 보정(?)시킨 것으로 보인다. 이에 따라 다른 나라 사람들은 모르겠지만 유독 우리나라에서는 Korean Sort 방식의 호환성이라는 측면에서 문제로 인식될 밖에 없다. 그리고, 적어도 Windows Vista 코드에는 다음과 같은 Workaround 가능함을 통해서 Korean LCID 대한 OLD SORT 방식이 여전히 존재하는 것으로 확인되었다.

 

                  #define OLD_SORT 0x00080000

                  LCID LocaleLCID = 0x412 ; // ko-KR

 

                  int _tmain(int argc, _TCHAR* argv[])

                  {

                                   int iRet = CompareStringW(LocaleLCID, 0x0 | OLD_SORT,

                                                                        _T("A"), -1, _T(""), -1 );

                                   //[...]

 

Windows 7에서의 상위의 code 어떨까. 결과는 Vista에서와 동일한 동작을 하지만, Vista에서의 Workaround 적용되지 않는 . 오히려 Win7에서는 Sort Versioning이라는 방법을 통해서 과거 OS 호환성을 지원하고 있다. 그리고, 무엇보다 CompareStringW이라는 Function 대신하여 CompareStringEx 사용해야만 한다 번거로움도 있다.

 

                  const NLSVERSIONINFO w2k3SortVer = {sizeof(NLSVERSIONINFO), 0xFF, 0xFF} ;

                  LPCWSTR LocaleName = L"ko-KR" ;

 

                  int _tmain(int argc, _TCHAR* argv[])

                  {

                 

                 

                                   int iRet = CompareStringEx(LocaleName, 0, L"A", -1, L"",

                                                                       -1, (LPNLSVERSIONINFO)&w2k3SortVer, NULL, 0);

                                   //[...]

 

CompareStringEx 함수는 CompareStringW 유사하지만, 추가된 파라메터, NLSVERSIONINFO 넘겨주게 되어 있다. NLSVERSIONINFO 통해서 Sort Versioning 가능하게 한다. 일반적으로 NULL 넘기게 되면 Default OS NLSVersion 따르므로 그에 대한 Sort 방식을 사용한다. 문제가 되는 Windows Vista이전 OS Sort Order 고수하기 위해서는 상위의 코드처럼 NLSVERSIONINFO dwNLSVersion dwDefinedVersion 값을 0xFF 설정하여 Windows XP 또는 Windows 2003 OS에서의 OLD Sort 방식을 사용할 있도록 하는 것이 방법이다. 이는 Application Compat Issue 대한 일종의 Shim 있지만, Korean LCID 대해서 어떠한 Sort Order 사용하는 것이 맞는 것인지 한번쯤은 생각해 봐야 하다. [참고: http://blogs.msdn.com/michkap/archive/2007/10/08/5270854.aspx, "Sorting it all Out : A&P of Sort Keys, part 12 (aka Han sorts first!)" ]

by 강세윤 | 2010/01/13 11:22 | 정리되지 않은 기술 | 트랙백 | 덧글(0)
ArrayList와 List<T>에 대한 Instrumentation profiling 비교

확실히 .NET Framework 1.에서는 ArrayList 를 많이 사용한 반면에 2.에서는 Generic(예를 들어, List<T>) 으로 대체된 느낌이다. 특히, List<object> ArrayList Mixed Type element를 저장할 수 있으며, Size가 동적이라는 점에서 매우 유사하다. 성능은 어떤가? 실제로 아래의 Code를 가지고 Visual Studio 2010에서 제공하는 Profiler 특히, 수행속도 측면을 고려하기 위해 Instrumentation Profiling Report 를 수집해봤다.

 

            /*   ArrayList */

 

            ArrayList al = new ArrayList();

 

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

                al.Add(i);

 

            foreach (int el in al)

                total += el;

 

 

/* List<T> */

 

            List<object> li = new List<object>();

 

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

            {

                li.Add(i);

            }

 

            foreach (int el in li)

                total += el;

 

사실 몇 가지 고민을 한것이 ArrayList List<T>의 경우 비교시에 List<int> 라고 하지 않고 List<object>라고 한 것은 boxing/Unboxing이 미치는 영향 때문이었다. 비교해보면, (아래는 Compare performance reports를 통해서 출력한 정보이다.)

 

Comparison Column

Delta

Baseline Value

Comparison Value

ConsoleApplication2.Program.Main(string[])

11,354.76

0.00

11,354.76

System.Collections.IEnumerator.get_Current()

4,759.39

0.00

4,759.39

System.Collections.IEnumerator.MoveNext()

4,738.06

0.00

4,738.06

System.Collections.ArrayList.Add(object)

3,125.54

0.00

3,125.54

System.Collections.Generic.List`1.Add(!0)

-5,556.14

5,556.14

0.00

get_Current()

-6,343.00

6,343.00

0.00

MoveNext()

-7,230.91

7,230.91

0.00

ConsoleApplication1.Program.Main(string[])

-16,339.14

16,339.14

0.00

 

상위의 delta +로 표시된 부분은 모두 ArrayList에서 Function , Elapsed Exclusive Time(Time spent in this function (msec))을 출력한 결과이다. 제법 ArrayList Elapsed Time에서 좋은 결과를 가져오고 있다. Generic에서의 System.Collections.Generic.List`1.Add() Generic Type Object로 했기 때문에 boxing이 발생할 것 같고, Current, MoveNext에서는 Unboxing이 일어날 것 같은데, 그것만으로 생각하기엔 모두 결과가 좋지 못하다. ArrayList가 boxing/Unboxing으로 인한 성능저하를 더 가져올 것이라 생각했다. 그것보다 사실은 List<object>를  List<int>로 변경한 후에 기대감을 갖고 확인해보면 아래와 같다.

 

Comparison Column

Delta

Baseline Value

Comparison Value

System.Collections.IEnumerator.MoveNext()

-4,738.06

4,738.06

0.00

System.Collections.IEnumerator.get_Current()

-4,759.39

4,759.39

0.00

System.Collections.Generic.List`1.Add(!0)

3,320.21

0.00

3,320.21

System.Collections.ArrayList.Add(object)

-3,125.54

3,125.54

0.00

MoveNext()

3,185.60

0.00

3,185.60

get_Current()

2,004.02

0.00

2,004.02

ConsoleApplication2.Program.Main(string[])

-11,354.76

11,354.76

0.00

ConsoleApplication1.Program.Main(string[])

12,940.26

0.00

12,940.26

 

보면, 사실 좀 의아하다. ArrayList Add 하는 것에 비해 Generic.List Add 하는 것이 월등이 좋은 성능을 보여야 하는 것이 아닐까 했는 데, 결과는 좀 그렇다. MoveNext나 get_Current는 아주 조금 더 나은 성능을 보인다. 물론, 데이타의 양이 증가하면 더 차이가 날 수도 있겠지만 그외 다른 부분에서도 그리 감동적이지는 못하다. (그래도, Generic을 사용해야 ... 또한 .NET Framework 1.에서는 Generic 을 지원하지 않는 다는 것이다. 그러므로, 성능과 상관없이 1.에서는 List<>가 필요한 부분에 ArrayList를 쓸 수 밖에 없다.) 

 

+) Visual Studio 2010에서는 Compare Performance Reports 라는 기능을 제공한다. 먼저, Visual Studio 2010 Analyze 라는 메뉴에 보면, Launch performance wizard 가 있는 데, 이것을 통해서 profiling data(.vsp)를 생성할 수 있다. 이것은 일반적인 CPU 관련 Bottleneck 확인을 위한 CPU Sampling(recommended)이나 Memory Allocation관련 성능확인을 위한 .NET Memory Allocation(Sampling) 그리고, Concurrency 관련 Profiling을 할 수도 있으며, 상위에 Function Call Count timing 관련한 부분을 Profiling 하기 위한 Instrumentation mode가 존재한다. 이렇게 Profiling Data를 출력하면, 출력된 Profiling 데이타를 비교할 수 있도록 Analyze 메뉴에 따로 Compare Performance reports 기능이 제공된다. 관련해서는 http://msdn.microsoft.com/en-us/magazine/cc337887.aspx 를 참조하면 좋을 듯 하다.

 

by 강세윤 | 2010/01/05 15:09 | 정리되지 않은 기술 | 트랙백 | 덧글(0)
2010년의 생각을 열다 (불안에 대한 적절한 태도)

2010년 올해는 마음을 비우는 한해가 되었으면 좋겠다. 삶과 죽음이 점하나로 이뤄진 찰라의 시간동안 그리 대단하다고 연연하며 사는 지 마음을 비우고 살아야하는 데 그게 잘 되지 않는다. 삶의 기쁨이나 죽음의 슬픔이나 매 한가지로 자연에서 나서 자연으로 돌아가는 것을 잊지 않았으면 좋겠다. 지식을 쌓는 것이나 부를 쌓는 것이나 희노애락에 연연하거나 그것이 있는 곳에 마음이 있듯이 모두 다 덜어내야 유아의 웃음을 찾을 수 있을 것 같다. 마음을 거울 같이 깨끗이 하여 보여주되 무엇하나 머무르지 못하게하고, 있는 듯 없는 듯하여 자연(自然-스스로 그러하게)스럽게 사는 것이 좋겠다.

by 강세윤 | 2010/01/01 10:06 | 정리되지 않은 생각 | 트랙백 | 덧글(0)
< 이전페이지 다음페이지 >