Test Automation Again

제목을 Test Automation이라 쓰려다가, 예전에도 비슷한 제목의 글을 썼던 것이 떠올라서 찾아봤다.
그 때도 나름대로 perl을 썼었구나.
지금 보니, perl script 그 자체는 아니고, bash script에 perl 명령이 들어가 있는 것이었다.

최근에 한 (그리고 지금도 하고 있는) 실험은, NAS benchmark에 대해 software prefetch의 성능을 측정하는 것이었다.
Topic streaming (혹은 multicore networking? 1월부터 연구해 오던 것인데 정확한 제목이 없다.ㅎㅎ) 프로젝트에서 network constrained scenario에서는 우리 기법이 software prefetching보다 좋다는 것을 주장하고 싶은데, 그러기 위해서는 보통 scenario에서 software prefetch가 얼마나 좋은지 확인 해 볼 필요가 있었다.
흥미롭게도 이미 software prefetch는 gcc에서도 지원을 하고, Sun Niagara2 server에서 쓰는 Sun compiler에서는 default로 enable 되어 있다.  (-xprefetch=no%auto로 끌 수 있다)

간단히 software prefetch에 대해서 얘기하면,


- CPU의 속도가 memory의 속도보다 훨씬 빠르기 때문에 현대 CPU는 여러 단계의 memory hierarchy를 가진다
- On-chip memory는 cache라고 부르며, 이도 L1,L2,L3등 몇 단계로 나뉜다 (L1이 가장 빠르고, L3가 가장 크다; L3 없이 L1,L2만 있는 경우도 많다)
- 프로그램이 실행되면서 특정 memory가 필요하면, 차례대로 L1,L2,L3 cache를 찾고, 그래도 못 찾으면 RAM에서 찾는다
- (당연히) on-chip인 cache보다 off-chip인 RAM이 훨씬 느리다.  (going to RAM incurs 50 cycle stalls?)
- Sun Niagara2 server의 경우 shared L2가 4MB이고, RAM은 128GB로 32768배 크다;

쓰다보니 그리 간단하지 않군; 위와 같이 RAM이 cache보다 훨씬 느리기 때문에, 미래에 특정 memory가 필요할 것을 알면, 미리 그 값을 cache로 불러들이는 것이 software prefetch이다.
ASPLOS 92: Design and evaluation of a compiler algorithm for prefetching이 대략 seminal paper임.
Software prefetch가 가능하기 위해서는 HW에서 그에 상응하는 instruction을 제공해야 하는데,

http://gcc.gnu.org/projects/prefetch.html

에서 보듯 대부분 major architecture는 이미 그러한 ISA를 제공하고 있다.  Architecture이 얼마나 복잡한지 다시 새삼 느끼고,
so compiler research IS used in the real world!

Perl scripting에 관한 글을 쓰다가 논점에서 샌 것은 이 정도로 정리하고;
8개의 benchmark가 있고, 각각 input size에 따라 class W,A,B,C 등으로 나뉘고, 1,4,16,64 threads에 대해 측정을 했다.
각 test가 짧게는 수초, 길게는 한 시간까지 걸릴 수 있는데, 이를 매번 직접 실행시키고, 결과를 기록하는 것은 참 많은 수작업이다.
사실, 자동화시킬까 말까 좀 생각하다가, 처음에는 그냥 이렇게 했는데, 교수님께서 test당 한번만 실행시키는 것은 오차 범위가 크다는 말씀을 하셔서;  이걸 또 경우마다 5번씩 수작업으로 할 수는 없잖아; 그래서 perl을 할까 python을 할까 (python도 이런 자동화를 위한 언어 맞지?) 5초 생각하다가, perl을 좀 배워보기로 결심!
3-4시간 정도의 코딩 끝네 다음과 같은 perl script를 완성했다:

use strict;
use warnings;
use Cwd;

print “Prefetch test script\n”;

my $runs = 5;
my $bindir = cwd();

my @benchmarks = (“ep”, “cg” ,”ft”, “mg”);
my @classes = (“A”, “B”, “C”);
my @numts = (1, 4, 16, 64);

if ($#ARGV == 0 && $ARGV[0] eq “make”) {
 system(“rm -f ./bin/*”);
 foreach (@benchmarks) {
  my $benchmark = $_;
  my $dir = uc($benchmark);
  chdir(“./$dir”);
  foreach (@classes) {
   my $class = $_;
   print “make $dir CLASS=$class”;
   my $ret = system(“make $dir CLASS=$class”);
   if ($ret != 0) {
    die “make failed\n”;
   }
  }
  chdir(“../”);
 }
}

system(“rm -f prefetch.csv”);

foreach (@benchmarks) {
 my $benchmark = $_;
 foreach (@classes) {
  my $class = $_;
  foreach (@numts) {
   my $numt = $_;
   $ENV{‘OMP_NUM_THREADS’} = $numt;
   open(my $outFile, “>>”, “prefetch.csv”);
   print “$benchmark,$class,$numt\n”;
   print $outFile “$benchmark,$class,$numt,”;
   for (my $run = 0; $run < $runs; $run++) {
    my $executable = "$benchmark.$class";
    print "\t run $executable $run\n";
    my $ret = system("./bin/$executable > output”);
    #my $ret = system(“./bin/$executable”);
    if ($ret != 0) {
     die “run failed\n”;
    }
    open(my $inFile, “<", "output");
    my @lines = <$inFile>;
    foreach (@lines) {
     my $line = $_;
     if ($line =~ /Time in seconds/ && $line =~ /(\d+.\d+)/) {
       my $time = $1;
       print “\t time: $time\n”;
       print $outFile “$time,”;
       if ($time > 1000) {
        $run = $runs;
       }
     }
    }
    close($inFile);
   }
   print $outFile “\n”;
   close($outFile);
  }
 }
}

if ($#ARGV == 0 && $ARGV[0] eq “make”) {

와 같이 argument가 하나이고 그 값이 make이면, bin 디렉토리를 비우고, 필요한 benchmark를 모두 make 하도록 했다.
Perl에서는 argument가 없을 경우 $#ARGV가 -1이고, 하나이면 0, 둘이면 1.. 이런식으로 나간다.

측정하고자 하는 대상들을 다음과 같이 array로 만들고

my @benchmarks = (“ep”, “cg” ,”ft”, “mg”);
my @classes = (“A”, “B”, “C”);
my @numts = (1, 4, 16, 64);

foreach (@benchmarks) {
 my $benchmark = $_;

와 같이 $_으로 array의 현재 값을 손쉽게 찾을 수 있는 것이 편리하다.

for (int i = 0; i < benchmarks.length; i++) {
  ... = benchmarks[i]

와 같이 typing을 많이 할 필요가 없고.ㅎㅎ

$ENV{'OMP_NUM_THREADS'} = $numt;

한 줄로 손쉽게 thread 개수를 정하는 환경 변수를 설정할 수 있다.

그래도 Perl의 가장 큰 강점은 regular expression일텐데, 모든 NAS benchmark는 실행 시간을

Time in seconds:  12.34

와 같이 출력한다.

    my @lines = <$inFile>;
    foreach (@lines) {
     my $line = $_;
     if ($line =~ /Time in seconds/ && $line =~ /(\d+.\d+)/) {
       my $time = $1;

이를 단번에 위와 같은 다섯 줄로 추출할 수 있다는 것이 멋지다.  실행 출력을 읽어들이고, 각 줄을 $line에 저장한다.
그리고 Time in seconds 라는 문구가 줄에 있으면, \d+.\d+의 regular expression을 만족하는 string을 추출한다.
\d가 숫자이고, +는 ‘하나 이상’이라는 의미.  즉 숫자 하나 이상 + 소숫점 + 숫자 하나 이상 = floating number가 되는 것이다.

마지막으로 추출한 실행 시간 값들을 comma separated file로 저장하면, Excel에서 바로 읽어서 format할 수 있다.ㅎㅎ

Perl이 이런 언어구나.  확실히 이러한 efficiency가 중요하지 않은 (script가 다른 프로그램들을 연결시켜 주는 것이므로, 그 자체가 memory를 조금 더 먹거나, 조금 더 느린 것은 무의미하다) application에는 Perl과 같은 non-typed, 그리고 즉석에서 다양한 기능을 제공하는 언어가 좋은 것 같다.

This entry was posted in Electrical Engineering. Bookmark the permalink.

4 Responses to Test Automation Again

  1. aero says:

    몇가지 참견? 드리자면

    if ($#ARGV == 0 && $ARGV[0] eq “make”) {

    는 배열이 스칼라컨텍스트에서 평가되면 요소의 갯수이므로

    if (@ARGV == 1 && $ARGV[0] eq “make” ) {

    처럼 하는 게 인자가 1개가 있다는걸 채크하는데 더 직관적일 것 같습니다.

    그리고
    my @benchmarks = (”ep”, “cg” ,”ft”, “mg”);
    my @classes = (”A”, “B”, “C”);

    같은 문자열 리스트는 qw 연산자를 사용해서 “some”, 같은 “,노가다 없이

    my @benchmakrs = qw/ep cg ft mg/;
    my @classes = qw/A B C/;

    처럼 간단하게 쓸 수 있습니다.

    그리고

    foreach (@benchmarks) {
    my $benchmark = $_;

    는 따로 $_ 기본변수를 루프내에서 할당 하지 않아도

    foreach my $benchmark (@benchmarks) {

    처럼 하면 my $benchmark 에 차례대로 들어가며 자동으로
    그 변수의 영역이 foreach 루프내의 렉시컬 영역으로 한정됩니다.

    my @lines = ;
    foreach (@lines) {
    my $line = $_;
    if ($line =~ /Time in seconds/ && $line =~ /(\d+.\d+)/) {
    my $time = $1;

    는 따로 @lines 배열에 넣고 foreach 돌리실 필요없이

    while ( my $line = ) {
    if ($line =~ /Time in seconds/ && $line =~ /(\d+.\d+)/) {
    my $time = $1;

    처럼 하시면 더 간단하겠네요.

    그 외에도 모듈들을 사용하면 더 간단하고 멋지게 할 수 있을 것 같지만
    그건 다음에~ :)

  2. 영준 says:

    오오 감사합니다!
    말씀하신 사항들 모두 반영해서 잘 쓰고 있습니다 :D
    역시 perl은 간단하네요
    배워가는 김에, 혹시 system으로 다른 프로그램을 돌릴 때 ctrl-c를 받으면 항상 exit 하게끔 하는 기능은 없나요? 아니면 강제 종료를 위해서는 ctrl-c를 정말 많이 입력해야 해서요

    my $ret = system(“./bin/$executable > output”);
    if ($ret != 0) {
    die “run failed\n”;
    }

    우선은 위와 같이 했습니다만, 좀 더 간단한 방법이 있다면 조언 부탁 드립니다!

    더불어, /* */와 같은 block comment가 없는 듯 한데; 주로 어떻게 하시나요?

  3. aero says:

    ctrl-c 문제는 저 같은 경우 겪어보질 못해서 직접 보기전에는 잘 모르겠구요

    block comment는 perl문서형식인 pod 테그를 사용해서

    =for
    very
    long
    comment
    =cut
    처럼 하시면 됩니다. 자세한건 http://dev.perl.org/perl6/rfc/5.html

  4. 영준 says:

    네 감사합니다 :D

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>