2014년 1월 26일 일요일

C# 메모리 관리 기법 - Boxing

가비지로 인한 부하도 크지만 박싱으로 인한 부하도 무시할 수 없습니다. 

박싱이란 것은 Value type 객체를 Reference type객체로 바꾸는 것을 말하는데요.

C#에서는 모든 객체가 object로부터 상속됩니다. 
int, float같은 Value type조차도 object로부터 상속받은 것처럼 사용할 수 있습니다.

이런것들을 주의하지 않고 코드를 짜게 될경우 의도치 않게 박싱이 일어나는 곳들이 생기게 되고 이것이 누적되면 무시할 수 없는 속도저하가 일어나게 됩니다. 

아래의 예를 보시면, 리스트에 서로 다른 여러 종류의 type 값을 추가하는데 foreach문 안에서 item을 object로만 받아서 처리하고 있는것을 보실 수 있습니다. 
class MyClass
{
    public override string ToString() 
    { 
        return "다섯"; 
    }

    static public void Sample()
    {
        ArrayList list = new ArrayList();

        // 리스트에 아래처럼 여러가지 타입의 값을 추가해 넣어줍니다.
        // int형 float형 string형 등등.....
        list.Add(1);
        list.Add(1.5f);
        list.Add(‘3’);
        list.Add("four");
        list.Add(new MyClass());

        // 리스트에서 값을 꺼낼때 오직 object로 받아서 처리하고 있습니다.
        // 이런 경우 Value type을 Reference type으로 바꿔주면서 시간이 오래 걸리고
        // 변환하면서 heap에 저장이 됩니다. 
        // 시간도 오래 걸리고 가비지도 생기게 되겠네요.
        foreach (object item in list)
        {
            Debug.log(item.ToString());
        }
    }
}
그래서 여러 type을 처리해야만 하는 경우가 아니라면 값의 type을 명시할 수 있는 Generic collection의 사용이 좋겠습니다. 
Generic은 C++의 template과 비슷해서 C++의 STL container와 비슷하게 생겼습니다. 

아래의 예제에서 잘 사용한 경우와 그렇지 않은 경우를 비교해서 보세요.
class Example
{
    // 일단은 여러가지 type을 사용하지 않도록 분리해서 작업하는것이 제일 좋습니다. 
    // 분리가 끝나면 적절한 type을 명시할 수 있는 Generic collection을 사용하구요.

    // 좋지 못한 경우
    static public void BadCase()
    {
        // type에 대한 명시가 없는 어레이 리스트를 사용합니다.
        ArrayList list = new ArrayList();

        // 값은 int형인데
        int evenSum = 0;
        int oddSum  = 0;

        for (int i = 0; i < 1000000; i++)
        {
            list.Add(i);
        }

        // item을 object로 받아서 처리하게 되네요
        // 박싱이 일어납니다.
        foreach (object item in list)
        {
            if (item is int)
            {
                int num = (int)item;

                if(num % 2 ==0)
                {
                    evenSum += num;
                }
                else 
                {
                    oddSum += num;
                }
            }
        }
           
        Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
    }

    // 적절하게 잘 사용한 경우
    static public void GoodCase()
    {
        // type을 명시해줬습니다.
        List<int> list = new List<int>();

        // int형을 사용했구요
        int evenSum = 0;
        int oddSum  = 0;

        for (int i = 0; i < 1000000; i++)
        {
            list.Add(i);
        }

        // num을 int형으로 받아서 처리합니다. 
        // 박싱이 일어나지 않습니다.
        foreach (int num in list)
        {
            if (num % 2 == 0)
            {
                evenSum += num;
            }
            else
            {
                oddSum += num;
            }
        }
           
        Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
    }
}





C# 메모리 관리 기법 - instance

C#에서 클래스를 인스턴싱할때는 new를 해서 할 수 있습니다. 그런데 이런 식으로 생성하게 되면 heap에 메모리가 할당됩니다. 

그리고 이런것들이 계속 누적이 되면 가비지가 작동하게 됩니다. 

아래 예를 보세요.
static class TestMyVector
{
    public static void PrintVectorLength(float x, float y, float z)
    {
        // 이런것들이 heap에 메모리가 할당됩니다. 
        Vector3 v = new Vector3(x, y, z);
        Debug.log("v의 값 X = " + v.x  + "V의 값 Y = " + v.y + "V의 값 Z = " + v.z);
    }
}
이런 경우에 Vector 클래스를 구조체로 바꾸면 new를 통해서 생성하게 되도 heap에 메모리가 할당되지 않게 됩니다. 
구조체는 Value type이기때문에 stack에 할당되기 때문입니다. 
이런식으로 해서 가비지를 조금이라도 줄일 수 있습니다. 
아래의 예처럼요.
public class MyVector
{
    // 이런식으로 구조체를 만들고
    public struct MyVector
    {
        public float x, y, z;

        public MyVector(float x, float y, float z) 
        { 
            this.x = x;
            this.y = y; 
            this.z = z;
        }
    }
}

static class TestMyVector
{
    public static void PrintVectorLength(float x, float y, float z)
    {
        // 구조체를 이용해서 인스터스하면 이것은 stack에 할당됩니다.
        MyVector v = new MyVector(x, y, z);
        Debug.log("v의 값 X = " + v.x  + "V의 값 Y = " + v.y + "V의 값 Z = " + v.z);
    }
}
그런데 경우에 따라서는 구조체로 만드는 것이 애매한 경우가 있습니다. 
이런 경우엔 멤버변수로 만들어서 사용하는 방법이 있습니다. 
저도 주로 이 방법을 사용하는데요.. 가장 간편하기도 하고 멤버변수로 만들 경우 메서드 인자를 줄일수도 있고 여기 저기 사용하기 편하다는 장점도 있습니다. 
대신 잘못했을 경우 값이 도중에 바뀌어서 애매한 버그를 만들기도 합니다. ^^;;
하지만 개인적으로는 이 방법을 추천합니다. 
아래와 같이 멤버변수로 만들어서 사용할 수 있습니다. 
static class TestMyVector
{
    // 멤버변수로 만들어 놓습니다. 
    Vector3 tempVector3 = new Vector3(0.0f, 0.0f, 0.0f);

    // 이해를 돕기 위해 위와같이 만들었지만 x, y, z의 값이 모두 0인 경우
    // new를 사용하지 않고 
    // Vector3 tempVector3 = Vector3.zero
    // 이와 같이 쓸수도 있습니다. 

    public static void PrintVectorLength(float x, float y, float z)
    {
        tempVector3.x = x;
        tempVector3.y = y;
        tempVector3.z = z;
        Debug.log("tempVector3의 값 X = " + tempVector3.x  + "tempVector3의 값 Y = " + tempVector3.y + "tempVector3의 값 Z = " + tempVector3.z);
    }
}


2014년 1월 23일 목요일

C# 메모리 관리 기법 - string

유니티는 다양한 언어를 지원해서 개발을 좀더 수월하게 해주는데 저는 주로 Java보다 C#을 애용합니다. 

Java가 주는 간결하고 자유로움이 매력적이긴 하지만 개발이 진행될수록 그 자유스러움이 오히려 독이 되는 경우가 많다고 느껴지기 때문인데요. 

C#을 다루면서 참 코딩이 쉽다라고 느낀적이 참 많을 정도로 좋은 언어인 것은 틀림없습니다.

C#의 장점이라 할수 있는 특징들을 보면

  • 메모리 해제에 신경 쓰지 않아도 됩니다.
  • 이미 삭제된 메모리에 접근하는 실수를 방지해줍니다.
  • 잘못된 캐스팅으로 엉뚱한 메모리에 접근하지 않게 합니다.
  • 배열 크기보다 큰 메모리에 접근하지 못합니다.
  • 메모리 단편화에 대해 신경 쓰지 않아도 됩니다.
등등이 있겠네요. 
이런 장점들때문에 메모리를 무시하고 막 만들다 보면 가비지 컬렉션때문에 랙이 발생하는 경우가 다들 한번씩은 있을거에요.

어쨌든 C#을 사용하면서 알아두면 좋을 메모리 관리 기법에 대해서 써볼께요.
앞으로 하드웨어가 더 발전하면 메모리 관리도 필요 없어지지 않을까 싶네요. ^^;;

주기적으로 랙이 막 생기는데 로직의 이상도 없고 다른 부분에서는 특정 이상이 발견되지 않는다. 


그런데!! 

프로파일러를 돌려보니 System.GC.Collect()에서 호출 시간이 오래걸린다!!!

이거 십중팔구 가비지 컬렉션 문제입니다.


그럼 가비지 컬렉션이 작동하는 횟수를 줄여서 랙 발생 횟수를 줄이면 되겠죠. 

그러려면 가비지가 최대한 발생하지 않도록 해주어야 합니다.

그렇게 하기 위한 방법들중 string에 대해서 살펴보겠습니다.


C#은 문자열 조합하는 방법이 너무 쉽습니다. 


그래서 아래처럼 무턱대고 '+' 연산자를 이용해서 문자열을 만드는 경우가 많은데 이렇게 하면 '+'할때마다 새로운 string 인스턴스가 생성됩니다. 


그리고 이게 곧 가비지가 됩니다.


class Names
{
    public string[] name   = new string[100];

    public void Print()  
    {
        for (int index = 0; index < name.Length; index++)
        {
            // 아래처럼 '+'를 마구 쓰면 이게 다 가비지가 됩니다.
            string output  = "[" + index + "]" + name;
            Console.WriteLine(output);
        }
    }
}
이럴때 아래처럼 System.Text.StringBuilder를 사용하면 이런 문제를 해결 할 수 있습니다.

class NewNames
{
    public string[] name              = new string[100];
    private StringBuilder sb          = new StringBuilder();

    public void Print()
    {
        sb.Clear(); 

        for (int index = 0; index < name.Length; index++)
        {
            sb.Append("[");
            sb.Append(index);
            sb.Append("] ");
            sb.Append(name);
            sb.AppendLine();
        }
        
        Console.WriteLine(sb.ToString());
    }
}
이미 잡아 놓은 메모리 공간에 문자열을 복사해서 한번에 ToString()으로 string 객체를 생성해내기 때문에 가비지가 발생하지 않습니다.

Append()가 너무 많아 코드가 드럽다고 느껴지면 AppendFormat()을 사용할 수 도 있습니다.


class NewNamesOther
{
    public string[] name              = new string[100];
    private StringBuilder sb          = new StringBuilder();

    public void Print()
    {
        sb.Clear(); 

        for (int index = 0; index < name.Length; index++)
        {
            sb.AppendFormat("[{0}] {1}", index, name.ToString());
        }
        
        Console.WriteLine(sb.ToString());
    }
}
string처럼 Immutable pattern을 사용한 객체들의 값은 기존 메모리를 수정하지 않고 새로운 메모리를 만들어 반환하므로 주의해야합니다.

폼메일 결과를 메일로 받기

구글드라이브로 만든 폼메일을 사용자가 작성했을때 그 내용이 관리자에게 전달되지 않으면 매번 구글 드라이브에 접속해서 그 내용을 확인해야 합니다.

이게 여간 불편한게 아니죠. 이래서는 애써 폼메일을 만든 의미가 없습니다. 


이럴때 구글 스크립트를 이용하면 간단하게 해결이 됩니다. 

구글 스크립트를 이용해서 사용자가 폼메일을 작성하면 그 내용이 관리자에게 메일로 보내지게 만들어 볼께요.

구글 스크립트는 구글에서 제공하며 Java Script를 기반으로 하고 있어 Java와 유사합니다. 

또한 구글 드라이브(https://drive.google.com/)와 구글 사이트 도구(https://sites.google.com/)등 구글의 다양한 서비스에서 사용할 수 있는 스크립트 언어입니다. 


https://developers.google.com/apps-script/

위의 주소에서 보다 자세한 설명과 API등을 확인할 수 있습니다. 


자, 이제 시작해볼께요.




폼메일을 만들면서 만들었던 응답 스프레드 시트를 클릭해서 열어줍니다.
그러면 아래와 같은 그림이 나오는데 여기서 'Script editor'를 클릭합니다.






자, 위와 같은 그림이 나오면 'Blank Projet'를 클릭합니다.



위의 그림이 빈 프로젝트의 초기 화면인데 여기서 코드를 모두 지우고 아래와 같은 코드를 새로 넣는겁니다.

function onFormSubmit(e) 
{
    MailApp.sendEmail("이메일 주소", "메일 제목", e.values);
}

이메일 주소(관리자(자신)의 이메일 주소)와 메일 제목(메일이 올때의 메일 제목)은 적절하게 넣어주세요.

'e.values[1]'는 응답시트의 첫번째 열의 타임 스탬프를 제외한 첫번째 행을 뜻합니다. 
만약 두번째 행을 표시하려면 'e.values[2]'와 같이 쓸수 있습니다.

제목 부분을 클릭하면 제목을 수정할 수 있으니 필요에 따라 적당한 제목으로 바꿔주시고 모든 작업이 끝났으면 이 스크립트가 동작할 순간을 정해주면 됩니다. 



위의 그림처럼 'Current project's triggers'를 클릭하면 아래와 같은 그림이 나옵니다.


양식을 제출할때 응답내용을 메일로 보내라는 뜻입니다. 모두 끝났으면 'Save'를 클릭해 끝
내줍니다.
만야겡 아직 아무런 트리거도 설정되어 있지 않다는 메세지가 나올경우엔 방금 만든 트리거를 추가해주세요.

메일을 발송하는 스크립트가 포함된 경우엔 인증이 필요합니다.
그래서 인증이 필요하다는 메세지가 나올수 있는데 간단히 '인증'버튼을 눌러주면 모두 끝납니다.

모든 과정이 끝났으니 테스트를 해보시고 안되면 다시 처음부터 차근차근 따라해보세요.


2013년 12월 26일 목요일

Encrypt and Decrypt the XML file(XML 데이터 암호화)

// 데이터 암호화
public static string Encrypt (string toEncrypt)
{
  byte[] keyArray  = UTF8Encoding.UTF8.GetBytes ("12345678901234567890123456789012");

  // 256-AES key
  byte[] toEncryptArray   = UTF8Encoding.UTF8.GetBytes (toEncrypt);

  RijndaelManaged rDel  = new RijndaelManaged ();
  rDel.Key              = keyArray;
  rDel.Mode             = CipherMode.ECB;

  // http://msdn.microsoft.com/en-us/library/system.security.cryptography.ciphermode.aspx
  rDel.Padding   = PaddingMode.PKCS7;

  // better lang support
  ICryptoTransform cTransform  = rDel.CreateEncryptor ();

  byte[] resultArray   = cTransform.TransformFinalBlock (toEncryptArray, 0, toEncryptArray.Length);

  return Convert.ToBase64String (resultArray, 0, resultArray.Length);
}
 
 // 데이터 암호 해독
public static string Decrypt (string toDecrypt)
{
  byte[] keyArray  = UTF8Encoding.UTF8.GetBytes ("12345678901234567890123456789012");

  // AES-256 key
  byte[] toEncryptArray   = Convert.FromBase64String (toDecrypt);

  RijndaelManaged rDel  = new RijndaelManaged ();
  rDel.Key              = keyArray;
  rDel.Mode             = CipherMode.ECB;

  // http://msdn.microsoft.com/en-us/library/system.security.cryptography.ciphermode.aspx
  rDel.Padding    = PaddingMode.PKCS7;

  // better lang support
  ICryptoTransform cTransform  = rDel.CreateDecryptor ();

  byte[] resultArray   = cTransform.TransformFinalBlock (toEncryptArray, 0, toEncryptArray.Length);

  return UTF8Encoding.UTF8.GetString (resultArray);
}