레이블이 programming인 게시물을 표시합니다. 모든 게시물 표시
레이블이 programming인 게시물을 표시합니다. 모든 게시물 표시

2021년 2월 1일 월요일

UE4 - Skookum script good example code : Death Of Tick

2015 년 6 월 26 일부터 2016 년 1 월 16 일까지 ~ 6 개월 동안 총 4,660 개의 컴파일을 컴파일하는 데 44.98 시간을 보냈습니다. 이것은 평균 34.75 초 / 컴파일이지만, UE4를 다시로드하고 핫 리로드가 실패했을 때 (약 25 % 확률) 포인터를 확인하는 것을 잊었을 때 작업했던 작업으로 돌아가는 데 걸리는 시간을 고려하지 않습니다. 언리얼 엔진 4의 C ++ 반복 시간에 대한이 엄청난 좌절감이 생겼었습니다.


제가 원했던 것은 UE4에서 사용할 수있는 간단한 스크립팅 언어로 C ++ 반복 시간을 줄이는 것이 었습니다.


SkookumScript에 대해 이야기하기 전에 Tick에 대해 논의해야합니다. 

게임 개발자로서 우리는 Tick에 살고 죽습니다. 

Tick은 우리가 게임에서하는 모든 일에 명백한 필수 요소이지만 틱이 얼마나 끔찍한 지 생각해 본 적이 있습니까?

Tick이 구현을 제어하는 ​​방법을 설명하기 위해 간단한 알고리즘을 살펴봅시다.


C++ - Tick 을 사용할 경우


유도 미사일이 발사되었을 때 다음 알고리즘을 사용하는 상상해보십시오. 여기에서 존재의 3 단계로 구분됩니다.


스테이지 1

  • 전방 방향으로 임펄스 적용

  • 0.5 초 동안 기다려

2 단계

  • 목표 획득

  • 타겟의 홈

3 단계

  • 목표물이 맞지 않으면 10 초 후 자살


틱 때문에 의사 코드는 다음과 같이 보일 수 있습니다.

Begin()
{
  Stage = 1
  FireImpulse()
  SetTimer Stage1Over for 0.5 seconds //Stage1Over에 0.5초 타이머 입력.
  SetTimer Stage2Over for 10 seconds  //Stage2Over에 10초 타이머 입력.
}

Tick()
{
  if Stage == 2
  {
    HomeToTarget()
  }
}

Stage1Over()
{
  Stage = 2
  AcquireTarget()
}

Stage2Over()
{
  DestroySelf()
}


알고리즘의 의도를 파악하는 것은 복잡성이 증가함에 따라 지루해질 수 있습니다.

우리는 이러한 복잡성과 상태 전환을 더 잘 구조화하는 데 도움이되는 패턴을 가지고 있지만,

결국 틱은 우리가 쉽게 이해할 수있는 알고리즘을 가져 와서 일부 표현주의 예술가가 캔버스에 페인트를 칠하는 것처럼 코드베이스에 흩뿌 리도록 강제합니다.



게임 로직 작성에 대한 전체 관점을 바꾸는 더 좋은 방법이 있습니다. 이 알고리즘에 대한 실제 SkookumScript (Sk) 코드는 다음과 같습니다.


()
[
  fire_forward_impulse
  _wait ( 0.5 ) //0.5초 딜레이.
  acquire_target

  race
  [
    _home_to_target
    _wait ( 9.5 ) // 위에서 이미 0.5초를 기다렸습니다.
  ]

  destroy_self
]

평범한 영어처럼 읽히기에 대충 무엇을 의미하는지 알 수 있겠지만, race [] 가 무엇인지 궁금하겠지요?


race → 말 그대로 경주하다에서 따온 명령.[                                       ] 사이의 명령어들 중 하나가 완료되는 즉시 다음 줄의 코드로 이동.


위 코드에서 race 함수의 결과 ex)

[

  race

  [

    _home_to_target

    _wait ( 9.5 ) // 위에서 이미 0.5초를 기다렸습니다.

  ]

  destroy_self

]


↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 위 코드에 대한 출력 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

_home _to_target 실행중...

_wait ( 9. 5 ) 실행중...

만약 타겟에 9.5초가 지났어도 도달하지 못했다면 ...  // 이부분에서 _home _to_target 이게 먼저 성공했는지 _wait ( 9. 5 ) 가 먼저 성공했는지  둘중 하나 먼저 성공한게 있다면 바로 빠져나와서 아래 코드 실행.
destroy_self ... 


발사 루프 예

다음 예제에는 _fire코 루틴 수정이 포함됩니다 . 이 코 루틴은 다음과 같이 무한 루프에서 호출됩니다.

loop
[
  if @is_firing? // @ indicates a member variable while ? indicates a boolean
  [
    _fire
  ]
  else [_wait] // wait 1 frame
]


fire_projectile 1.0 // 1.0은 발사체의 크기입니다.
_wait 0.15 )





즉시 vs 지연

Sk는 즉각적인 진술과 지연된 진술의 개념을 가지고 있습니다. 

즉각적인 문은 (지금까지 사용한 모든 함수와 마찬가지로) 동일한 프레임에서 완료되는 반면 지연된 문은 완료하는 데 두 개 이상의 프레임이 걸릴 수 있습니다. 

지연된 문을 코 루틴이라고하며 밑줄로 시작합니다. 

_wait지정된 시간 동안 대기하는 내장 코 루틴입니다.


_wait // 1 프레임 대기
_wait // 1 초 기다립니다


메서드 또는 코 루틴이 인수를 사용하지 않는 경우 (또는 기본 인수가 필요한 경우) 괄호가 필요하지 않습니다. 

위의 기본 예에서 즉시 문과 fire_projectile(1.0)지연 문이 모두 표시 _wait(0.15)됩니다.

_wait메서드 내에서 호출하려고 fire_projectile하면 지연된 문을 즉시 내에서 호출 할 수 없기 때문에 컴파일 오류가 발생합니다. 

그러나 _fire_projectile대기가 허용 되는 코 루틴 을 쉽게 만들 수 있습니다. 또는 branch즉시 내에서 새 코 루틴을 해제 할 수 있습니다


재 장전 지연이있는 3개의 미사일

// 기다릴 수있는 능력으로 무언가를 3 번 ​​수행
// 즉각적인 버전도 있습니다.
3. _do
[
	fire_projectile ( 1.0 )
	_wait ( 0.05 )
]
_wait ( 0.5 )




하다(do)

do 키워드는 말 그대로 n번 수행을 의미합니다.


5. do [ println ( "HI" )] // HI를 5 번 인쇄합니다.
3. do [ println ( idx )] // 012 출력

// 각각 1 초 간격으로 HI를 3 번 ​​인쇄합니다.
3. _do
[
	println ( "HI" )
	_wait ( 1 )
]


위 코드 중 3. do [ println ( idx )] 에서 idx란.

변수를 사용하여 반복 횟수를 지정할 수도 있습니다. .NET idxFramework에서 현재 인덱스를 제공하는 기본 제공 변수입니다.

variable.do[println(idx)] // prints 0 1 2 ... variable - 1


스프레드 샷

// 총 소켓의 회전을 가져옵니다.
//! 새 개체를 구성하는 데 사용됩니다.
// : 변수를 객체에 바인딩
// @는 개체의 멤버 변수에 액세스하는 데 사용되며 @rob_skeleton은
// Pawn 클래스의 일부인 스켈 레탈 메시 컴포넌트입니다.
// "LeftGunSocket".Name은 문자열을 내장 된 FName으로 변환합니다.
// socket_rotation 메소드가 필요합니다.

! rot : @rob_skeleton. socket_rotation ( "LeftGunSocket" .Name )

// 발사체의 회전 및 배율 전달
// rot. @ yaw는 RotationAngles 객체의 yaw 변수에 액세스합니다.

fire_projectile_degrees ( rot. @ yaw, 1.0 )
fire_projectile_degrees ( rot. @ yaw + 5.0 , 1.0 )
fire_projectile_degrees ( rot. @ yaw- 5.0 , 1.0 )

_wait ( 0.5 )




버스트 + 그랜드 피날레

3._do
[
  fire_projectile ( 1.0 )
  _wait ( 0.05 )
]

_wait( 0.3 )

100.do
[
  // idx >>는 idx (Integer)를 Real로 변환합니다. idx >> Real이라고 쓸 수 있지만 Sk는
  // 컨텍스트에 따라 올바른 유형으로 변환 할 수있을만큼 똑똑합니다.
  fire_projectile_degrees([360.0/100.0] * idx >>, 1.0 ) 
]

_wait( 1.0 )



버스트 + 서클 + 피날레


3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]

50._do
[
  fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
  _wait(0.05)
]

_wait(0.3)

100.do
[
  fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0)
]

_wait(1.0)



버스트 + 싱크 [서클 서클] + 피날레

이것은 뭔가 새로운 것이 있습니다. 당신이 그것을 발견하는지보십시오.


3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]

sync
[  
  50._do
  [
    fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
    _wait(0.05)
  ]
  75._do_reverse
  [
    fire_projectile_degrees([360.0 / 75.0] * idx>>, 0.8, green)
    _wait(0.05)
  ]
]

_wait(0.3)

100.do
[
  fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0)
]

_wait(1.0)




위의 코드 블록은 sync라는 기능을 사용중입니다.

sync는 병렬로 실행할 코루틴 블록을 지정할 수 있습니다. 실행은 sync블록의 모든  코루틴이 완료된 후에 만 블록을 넘어 계속 됩니다.

sync
[
   _routine1
   _routine2
   _routine3
]
// 루틴 1-3이 완료 될 때까지이 라인에 도달하지 않습니다.


제 경우에는 _do 2개의 코 루틴을 정의하는 데 사용 하고 있습니다. 

내가 좋아하는 패턴을 정하면 이것을 리팩토링 할 수 있으므로 _circular_pattern수량, 색상, 지연 및 스케일을 취하는 코루틴을 만들 수 있습니다 .

// 프로덕션 코드는 다음과 비슷합니다.
_burst_shot

sync
[
_circular_pattern(50, blue, 0.050.8)
_circular_pattern(75, green, 0.050.8)
]

_grand_finale


버스트 + 레이스 [서클 서클 대기] + 피날레

3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]

race
[  
  50._do
  [
    fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
    _wait(0.05)
  ]
  
  75._do_reverse
  [
    fire_projectile_degrees([360.0 / 75.0] * idx>>, 0.8, green)
    _wait(0.05)
  ]

  _wait(1)
]
  
_wait(0.3)

100.do
[
  fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0)
]

_wait(1.0)




경주

race병렬로 실행할 코 루틴 블록을 지정할 수 있습니다. 

때 어떤 이 코 루틴 출구에서, 다른 모든 코 루틴 race블록이 종료되고 실행은지나 계속 race차단합니다. 

말 그대로 모든 코 루틴이 가장 먼저 나가기 위해 경쟁하는 경주와 같습니다. 

예를 들어, 나는 같은 코드를 촬영 한 sync예와 race'는 대조를 거라고 _wait(1)

예상되는 것은 _wait(1)원형 발사 패턴 이전에 끝나므로 전체 레이스 블록은 1 초 후에 종료됩니다.

 race에 대한 또 다른 용도가 있습니다. 무엇을하는지 추측 해보세요.

race
[
   _wait_not_firing
   _fire
]

받았어? _fire사용자가 발사 버튼을 놓으면 전체 코 루틴을 중단 합니다.

어디 _wait_not_firing다음과 같습니다

()
[
   loop
   [
       [exit] when not @is_firing?
      _wait
   ]
]


분기(brach)

이 마지막 예제는 branch.  branch백그라운드에서 코 루틴을 실행하고 즉시 다음 문으로 계속 진행합니다. 

여기서는 그랜드 피날레 발사체에 몇 가지 추가 동작을 연결하는 데 사용합니다. 또한 sync문 에서 실행 패턴을 변경 하고 중첩을 허용하도록 기본 클로저 변수 이름을 재정의하는 예를 추가했습니다 _do(직접 조사 할 수 있음). 

색상을 가이드로 사용하여 각 문장이 무엇을하는지 확인하십시오.

3._do
[
  fire_projectile(1.0)
  _wait(0.05)
]
  
sync
[  
  50._do
  [
    fire_projectile_degrees([360.0 / 50.0] * idx>>, 0.8, blue)
    _wait(0.05)
  ]
 
  250._do_reverse
  [
    fire_projectile_degrees([[5.0 * 360.0] / 250.0] * idx>>, 0.8, green)
    _wait(0.01)
  ]
   
  4._do
  [
    5.do (Integer idx2)
    [
      fire_projectile_degrees([[90.0 / 5.0] * idx2>>] + [90.0 * idx>>], 2.0, purple)        
    ]
    _wait(0.8)
  ]
]
  
_wait(0.3)
100.do
[
  !p : fire_projectile_degrees([360.0 / 100.0] * idx>>, 1.0, purple)
  branch
  [
    _wait(0.2)
    if p.not_nil?
    [
      p._seek_actor(this) // this is the pawn that is firing these projectiles
    ]      
  ]
]

_wait(1.0)




마무리

: 그건 내가 Sk를의 기둥을 고려 무엇에 충돌 과정이었다 racesyncbranch와 _wait. 그러나 저는 언어에 포함 된 기능의 극히 일부만을 다루었습니다. 

바라건대, Tick을 제거하면 매우 표현적이고 간단한 알고리즘을 작성할 수있는 방법을 보셨을 것입니다.

나는 Sk가 반복하는 속도가 얼마나 빠른지 또는 강력한 명령 프롬프트로서 얼마나 유용한 지 보여주지 못했습니다. 

이 모든 발사 패턴은 라이브 러닝 게임에서 만들어졌으며 핫 리 컴파일에 문자 그대로 1 초도 걸리지 않았습니다. 

솔직히, 각 프로토 타입 사이에서 ESC를 반사적으로 치는 습관을 깨는 것은 언어 자체를 배우는 것보다 어렵습니다.


 출처 : https://error454.com/2017/03/09/the-death-of-tick-ue4-the-future-of-programming/

2019년 12월 17일 화요일

전위 증감 연산자(++i 와 i++)의 차이점

for 루프를 사용하다보면 자주 사용하게 되는 변수 i
이 i 변수에 1을 더하는 i++와 ++i는 서로 차이가 있다.
결론부터 말하면 ++i가 더 성능이 좋다고 할 수 있다.


Ex Code )

#include <stdio.h>
#include <time.h>

double be_test() {    
    clock_t start, end;
    int i,j;
    int sum = 0;
    start = clock(); //시간 측정 시작
    
    for (i = 0; i < 100000; ++i) {
        for (j = 0; j < 10000; ++j) {
            sum += i * j;
        }
    }
    end = clock(); //시간 측정 끝
    return (double)(end - start);
}


double af_test() {    
    clock_t start, end;
    int i,j;
    int sum = 0;
    start = clock(); //시간 측정 시작
    
    for (i = 0; i < 100000; i++) {
        for (j = 0; j < 10000; j++) {
            sum += i * j;
        }
    }
    end = clock(); //시간 측정 끝
    return (double)(end - start);

}

int main() {

    printf("전위 증감 연산 시간: %f", bf_test()); //결과 출력
printf("후위 증감 연산 시간: %f", af_test()); //결과 출력 return 0;
}

결과)


"전위 증감 연산 시간 : 22.000000"  (5번 정도 실행해본 결과 22초~ 23초)
"후위 증감 연산 시간 : 24.000000"  (5번 정도 실행해본 결과 24초~ 25초)


++i  :  내부적으로 다음과 같이 동작한다.

1. i의 값을 1 더한다.
2. i의 값을 반환한다.


i++ :  내부적으로 다음과 같이 동작한다.

1. i의 현재 값을 보관한다. (현재 실행되는 명령문에서는 이 보관된 값이 사용되어야 하니까)
2. i의 값을 1 더한다.
3. 보관했던 값을 반환한다.


2동작을 수행하는 것과 3동작을 수행하는 것(i++은 ++i에 보관하는 과정이 추가된 것이므로)은 많은 경우 성능 차이를 가져온다.

i++와 ++i는 현재 행에서 사용되는 값이 원래의 값을 사용하는가 1이 더해진 값을 사용하는가의 차이도 존재하지만
for(i = 0; i < 10; i++)에서와 같이 단독으로도 충분히 사용된다. 이러한 경우 i++보다는 ++i를 사용하는 것이 성능상 더 좋다고 할 수 있다.

물론, 똑똑한 컴파일러는 최적화를 수행해준다. i++로 코드를 작성했다고 하더라도 실행에 영향이 없는 내용은 ++i로 바꿔서 컴파일한다.

하지만! C++에서 연산자 오버로딩을 해서 전위, 후위 ++연산자를 직접 만든 경우에는 컴파일러가 최적화를 수행해 줄 수 없다.
때문에 이 경우 성능차이가 나게된다.
마찬가지로 C++ STL에서 vector, list 등을 사용할때 iterator를 전위 ++ 연산자로 증가시키면 성능상의 이점을 기대할 수 있다.

출처: https://iwantadmin.tistory.com/114 [실수로포스팅해버렸다.]

2019년 11월 16일 토요일

c# - 가비지컬렉터(GarbageCollector)사용시 주의해야 할 점

C# __6.0 가비지컬렉터(GarbageCollector)사용시 주의해야 할 점


이전 내용에서 가비지 컬렉션에 대해 알아보았으니, 이번에는 비지 컬렉터의 성능을 저하시키지 않기 위해 객체를 생성함에 있어서 조심해야할 부분 살펴보겠습니다.

첫번째로 객체를 너무 많이 할당하지 말아야 합니다. 
가장 기본적인 지침인데요, CLR 객체 할당 속도가 빠르긴 하지만 너무 많은 수의 객체는 관리되는 힙의  세대에 메모리 포화를 초래할  있고이는 빈번한 가비지 컬렉션을 부르는 결과를 낳습니다객체 할당 코드를 작성할   필요한 객체인지와 필요 이상으로 많은 객체를 생성하는 코드가 아닌지의 여부를 고려해야합니다.


두번째는 너무  객체 할당을 피해야 합니다. 
CLR 보통 크기의 객체를 할당하는 힙과는 별도로 85KB 이상의  객체를 할당하기 위한 '대형 객체 (LOH:Large Object Heap)' 따로 유지합니다. 우리가 평소에 사용하는 힙은 대형 객체 힙에 대비되는 개념으로 소형 객체 이라고 부르기도 합니다.

커다란 객체를 소형 객체 힙에 할당한다면 0세대가 빠르게 차오르게 되므로 가비지 컬렉션을  자주 촉발하게 되고이는 어플리케이션의 성능 저하를 불러오게 됩니다그래서 대형 객체 힙을 별도로 유지함으로  성능 향상의 효과를 내고 있습니다만단점도 있겠지요.

우선 대형 객체 힙은 동작 방식이 소형 객체 힙과 다릅니다대형 객체 힙은 객체의 크기를 계산한  그만한 여유 공간이 있는지 힙을 탐색하여 할당합니다. GC 수행한  소형 객체 힙은 해제된 메모리 공간에 인접 객체들을 끌어당겨 차곡차곡 정리하지만대형 객체 힙은 해제된 공간을 그대로 둔답니다매우  메모리를 복사하는 비용이 너무 비싸기 때문이지요.
 공간은 나중에 다른 객체들에게 할당 되겠지만메모리를 0바이트의 낭비도 없이 사용하는 소형객체 힙과는 달리  공간을 군데군데 낭비하게 됩니다.

그리고 또하나 CLR 대형 객체 힙을 2세대 힙으로 간주하기 때문에 대형 객체 힙에 있는 쓰레기 객체가 수거되려면 2세대에 대한 가비지 컬렉션이 수행되어야 합니다2세대 가비지 컬렉션은  세대에 대한 가비지 컬렉션을 유발하기 때문에 수거되는 메모리의 양이  수록 어플리케이션이 정지되는 경우가 발생하게 됩니다그래서 너무  객체를 할당하는 코드는 조심스럽게 고려해보아야 한답니다.


세번째는 너무 복잡한 참조 관계는 자제해야 합니다
이러한 지침은 성능 뿐아니라 가독성을 위해서라도 주의하셔야합니다참조 관계가 많은 객체는 가비지 컬렉션 후에 살아남았을 때가 문제입니다가비지 컬렉션에서 살아남았을 경우 다음 세대로 옮기기 위해 메모리 복사를 수행하는데  참조관계가 복잡한 객체의 경우에는 단순히 메모리를 복사하는데서 끝나지 않습니다객체를 구성하고 있는  필드 객체 간의 참조 관계를 일일이 조사해서 참조하고 있는 메모리 주소를 전부 수정한답니다 클래스 구조가 간단했다면 메모리 복사만으로도 끝났을 일을 탐색과 수정까지 거치게 되는것이죠.


네번째는 루트를 너무 많이 만들지 말아야 합니다. 
가비지 컬렉터는 루트 목록을 순회하면서 쓰레기를 찾아냅니다루트 목록이 작아진다면 그만큼 가비지 컬렉터가 검사를 수행하는 횟수가 줄어들므로  빨리 가비지 컬렉션을 끝낼  있습니다따라서 루트를 가급적 많이 많들지 않는 것이 성능에 유리하겠지요 내용은 필요 이상으로 객체를 만들지 말라는 기본적인 지침입니다.