티스토리 툴바

just backup

POST : development/ruby

루비 온 레일즈의 성능 문제

조엘의 블로그에 루비 온 레일즈(이하 RoR)의 성능 문제에 대한 글이 올라왔네요.

Ruby Performance Revisited

결국 하고자 하는 얘기는 개발자 비용과 CPU 비용 중 어느 것에 비중을 두느냐는 것이네요.

왜 우리 나라에서 RoR이 주류로 나설 수 없는 이유가 되는가에 대한 정답이 아닐까요?
이번 프로젝트에는 RoR을 적용해볼까 합니다.
뭐가 좋은데?
개발 시간을 대폭 단축할 수 있습니다.
오호! 그거 좋구먼. 한 번 해 봐. 그런데 안 좋은 거라도 있어?
상황에 따라 조금 느릴 수도 있습니다.
...... 하던 걸로 해.
물론 빠른 개발 시간이 최우선인 프로젝트도 있을 테고

성능 문제가 부차적인 프로젝트도 있을 겁니다.

하지만 대형 프로젝트에는 기존 프레임웍의 성능과 안정성을 뛰어넘지 않으면

많은 사람들에게 생소한 RoR을 선택하기는 어려울 겁니다.

더구나 프로젝트에 대한 결정권을 가지고 있는 사람들은 새로운 것 별로 안 좋아합니다. ㅡ,.ㅡ

RoR에서도 성능 향상을 위해 바이트코드 컴파일러를 개발한다는 얘기로 끝을 맺고 있으니

한 번 지켜봐야겠습니다. 뭐.. 그거 밖에 더 할 게 있겠습니까? -_-;
top

tags

Performance, Ruby

posted at

2006/09/13 11:58


POST : development/ruby

클로저

Closure를 달리 번역할 말이 없네요. 종결자? 종료자? ㅡ,.ㅡ

한동안 잊어버리고 있던 쥬크박스 프로젝트로 돌아와보죠.
쥬크박스에 버튼을 달아보려고 합니다. 기본적으로 시작, 중지 버튼을 생각해보죠.
각 버튼별로 처리과정이 다를테니 기본적인 버튼 클래스에서 상속받아 각 클래스를 만들어야겠죠.
class StartButton < Button
    def initialize
        super("Start")
    end
    def button_pressed
        # do start actions...
    end
end
start_button = StartButton.new
잠깐만! 이 짓거리를 버튼마다 해야 되는 겁니까? 리모콘은 구현할 엄두도 못 내겠군요. -_-;;
버튼을 눌렀을 때 구현할 부분만 살짝 넣을 수는 없을까요? 루비잖아욧!
class JukeboxButton < Button
    def initialize(label, &action)
        super(label)
        @action = action
    end
    def button_pressed
        @action.call(self)
    end
end
start_button = JukeboxButton.new("Start") {songlist.start}
pause_button = JukeboxButton.new("Pause") {songlist.pause}
여기서 주목해야 할 것은 &action 매개 변수입니다.
메소드의 마지막 매개 변수가 &로 시작하면 그 자리에 코드 블록을 찾아서 Proc 클래스로 변환합니다.
그 뒤에는 다른 매개 변수처럼 마음대로 쓸 수 있게 되는거죠.
여기서는 @action 변수에 저장한 뒤 button_pressed 메소드에서 Proc#call 메소드를 호출하는구요.

그런데 @action.call(self) 문장에서 self 지시어가 있습니다.
실제적으로 호출이 되는 것은 블록 안의 문장일텐데요? 이게 바로 클로저입니다.
블록 안에서 블록을 사용하는 곳에 정의된 self, 변수, 메소드를 사용할 수 있는 것을 말하는 겁니다.
이해하기 힘드실까봐 예제 하나 더 나갑니다.
def n_times(thing)
    return lambda {|n| thing * n}
end

p1 = n_times(23)
p1.call(3) # 69
p1.call(4) # 92
p2 = n_times("Hello")
p2.call(3) # Hello Hello Hello
일단 Kernel.lambda 메소드는 블록을 Proc 개체로 변환하는 기능을 합니다.
블록에서 사용하는 thing 매개 변수는 n_times 메소드가 호출된 후 사라져야 맞겠지만
p1.call 메소드에서 사용이 가능합니다. 이게 바로 클로저라는 것 알겠나요?
top

tags

Ruby

posted at

2006/08/22 16:09


POST : development/ruby

블록, 반복자 #2

Enumerable#find 외에 많이 쓰이는 다른 반복자도 살펴볼까요?
[1, 3, 5, 7, 9].each {|i| print i, " "} # 1 3 5 7 9
Array#each 메소드는 각 요소당 한 번씩 블록을 호출합니다.

['a', 'b', 'c'].collect {|x| x.succ}    # ['b', 'c', 'd']
Array#collect 메소드는 각 요소당 한 번씩 블록을 호출하여 배열을 반환합니다.

[1, 3, 5, 7].inject(0) {|sum, element| sum + element}   # 16
[1, 3, 5, 7].inject(1) {|sum, element| sum * element}   # 105
Enumberable#inject 메소드는 매개 변수가 있을 경우 그 값을 sum의 초기값으로
첫 번째 요소의 값을 element의 초기값으로 하여 블록을 실행시킵니다.

[1, 3, 5, 7].inject {|sum, element| sum + element}  # 16
[1, 3, 5, 7].inject {|sum, element| sum * element}  # 105
매개 변수가 없을 경우 첫 번째 요소의 값을 sum의 초기값으로
두 번째 요소의 값을 element의 초기값으로 하여 블록을 실행시킵니다.

다른 언어와 비교를 해보면 루비는 내부 반복자, 자바나 C++에서는 외부 반복자라는 것입니다.
루비에서는 반복자가 블록 안의 문장을 실행시키지만 자바에서는 Iterator 인터페이스를 이용합니다.
내부 반복자의 단점은 반복자를 개체로서 다룰 수 없다는 것입니다.
예를 들면 메소드의 반환값이나 매개 변수로 사용할 수 없다는 것이죠.
다행히 루비 1.8부터 Generator 라이브러리를 이용해서 외부 반복자를 구현할 수 있습니다.

지금까지 블록을 반복자와 같이 사용을 했는데요. 이번에는 블록의 다른 용도에 대해 알아보겠습니다.
데이터베이스에서 자주 얘기하는 트랜잭션(transaction), 연속된 작업들에 블록을 이용해보죠.
실제 데이테베이스에서 말하는 트랜잭션과 같은 의미는 아닙니다. 글재주가 없으니 먼저 코드를 보시죠.
File.open_and_process("16.rb", "r") do |file|
    while line = file.gets
        puts line
    end
end
File 클래스에 만들어진 open_and_process 메소드를 사용하는 코드입니다.
testfile 파일을 읽기 전용으로 읽어서 한 줄씩 그 내용을 출력해주는 내용이라는 것 대충 아시겠죠?
그런데 여기서 파일을 읽기만 하고 File.close() 정도로 추측되는 파일을 닫는 코드가 없습니다.
트랜잭션의 의미는 이처럼 파일을 닫는 작업을 File 클래스의 사용자가 아니라
File 클래스가 알아서 처리해주겠거니 생각하고 메소드를 사용하는 것을 말합니다.
구현 코드는 아래와 같습니다. 여기서 yield를 이용하여 블록을 사용하는 것이죠.
class File
    def File.open_and_process(*args)
        f = File.open(*args)
        yield f
        f.close()
    end
end
위의 경우처럼 파일을 닫는 작업을 알아서 처리해주는 건 행복한 일이지만 그걸 원하지 않는 경우도 있습니다.
파일을 닫지 않고 파일에 내용을 더 쓰고 싶을 수도 있으니까요. 그럴 때는 어떻게 할까요?
class File
    def File.my_open(*args)
        result = file = File.new(*args)
        if block_given?
            result = yield file
            file.close
        end
        return result
    end
end
Kernel.block_given? 메소드를 사용합니다. 이름에서 보이듯 블록인지 아닌지를 판단해주는 놈이죠.
top

tags

Ruby

posted at

2006/08/16 20:10


POST : development/ruby

블록, 반복자 #1

SongList 클래스에 제목으로 노래를 검색하는 with_title 메소드를 추가해 보겠습니다.
class SongList
    def with_title(title)
        for i in 0...@songs.length
            return @songs[i] if title == @songs[i].name
        end
        return nil
    end
end
for문을 사용하여 배열을 순환하면서 일치하는 제목을 찾습니다.
물론 이 코드도 아무 문제가 없지만 뭔가 다른 형식이 있을 것 같지 않나요? 이건 루비잖아요! ^^
class SongList
    def with_title(title)
        @songs.find {|song| title == song.name}
    end
end
뒤의 블록(block)을 순환하면서 실행하는 Enumerable#find 메소드가 반복자(iterator)입니다.
기초문법 #3에서 블록과 반복자를 얘기했던 기억이 나시나요? yield 메소드도 기억나실텐데요?
피보나치 수열을 예제로 작성해볼 테니 다시 기억을 되살려보세요.
def fib_up_to(max)
    i1, i2 = 1, 1
    while i1 <= max
        yield i1
        i1, i2 = i2, i1 + i2
    end
end

fib_up_to(1000) {|f| print f, " "}
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
오호라! 여러 변수를 한 줄에서 대입할 수도 있군요.

이번에는 블록 안팎에서의 변수 범위에 대해서 말해보죠.
a = [1, 2]
b = 'cat'
a.each {|b| c = b * a[1]}
a               # [1, 2]
b               # 2
defined?(c) # nil
  • 블록 밖에서 선언된 변수는 블록 안에서 그대로 사용 가능합니다.
  • 블록 안에서 처음 선언된 변수는 블록 안에서만 사용되는 지역 변수가 됩니다.
  • definded? 메소드를 통해 매개 변수가 선언되었는지 알 수 있습니다.
top

tags

Ruby

posted at

2006/08/16 18:45


POST : development/ruby

TestUnit

여기서 TestUnit라고 하는 테스트 프레임워크를 사용해 보겠습니다.
자바에서 JUnit라는 같은 일을 하는 넘을 보셨을지도 모르겠네요.
검색 사이트에서 테스트 주도 개발, TDD(Test-Driven Development)로 찾아보세요. ^^
뒤에 따로 설명할 기회가 있으니 간단하게 활용하는 코드를 실행시켜 보겠습니다.
require 'song'
require 'test/unit'
class TestSongList < Test::Unit::TestCase
    def test_delete
        list = SongList.new
        s1 = Song.new('title1', 'artist1', 1)
        s2 = Song.new('title2', 'artist2', 2)
        list.append(s1).append(s2)
        assert_equal(s1, list[0])
        assert_nil(list[9])
        assert_equal(s1, list.delete_first)
        assert_equal(s2, list.delete_first)
        assert_nil(list.delete_last)
    end
end
Loaded suite songlist
Started
.
Finished in 0.0 seconds.

1 tests, 8 assertions, 0 failures, 0 errors
  • assert_equal 메소드는 두 매개 변수의 값을 비교합니다.
  • assert_nil 메소드는 주어진 매개 변수가 nil인지 판단합니다.
top

tags

Ruby

posted at

2006/08/16 17:56


POST : development/ruby

SongList 클래스

지금껏 예제로 사용했던 Song 클래스를 리스트로 갖는 SongList 클래스를 구현해보겠습니다.
require 'song'
class SongList
    def initialize
        @songs = Array.new
    end
    def append(song)
        @songs.push(song)
        self
    end
    def delete_first
        @songs.shift
    end
    def delete_last
        @songs.pop
    end
    def [](index)
        @songs[index]
    end
end
  • Array 클래스의 메소드인 push, shift, pop이 보입니다.
  • self 지시어는 현재 개체에 대한 참조값입니다.
  • [] 연산자를 정의하고 있습니다. 연산자 정의도 메소드 정의와 형식이 같네요.
top

tags

Ruby

posted at

2006/08/16 16:50


POST : development/ruby

컨테이너

하나 이상의 다른 개체들을 참조하는 개체를 컨테이너(Containers)라고 합니다.
대표적 컨테이너인 배열과 해시에 대해서 잠시 알아보겠습니다.

배열(Array)

배열을 선언하는 방법은 두 가지입니다.
  1. 명시적으로 Array 개체를 생성합니다.
  2. 대괄호에 둘러싸여 있는 요소들을 지정하여 생성합니다.
a = Array.new
b = [3.14159, "pie", 99]
루비에서 배열의 첨자는 0부터 시작합니다. 또 길이가 가변적이므로 데이터가 없으면 nil을 반환합니다.
하지만 다른 언어와 구별되는 특이한 여러 가지 첨자 형식이 사용됩니다.

먼저 첨자가 음수일 경우 배열의 끝에서 거꾸로 진행됩니다.
a = [1, 3, 5, 7, 9]

a[-1]       # 9
a[-2]       # 7
a[-99]      # nil

첨자를 [시작위치, 갯수] 형식으로 사용할 수 있습니다.
a[1, 3]     # [3, 5, 7]
a[3, 1]     # [7]
a[-3, 2]    # [5, 7]

부분범위로 [시작위치..끝위치] 형식으로 사용할 수 있습니다.
마침표가 2개이면 끝위치를 포함하고, 3개이면 포함하지 않습니다.
a[1..3]     # [3, 5, 7]
a[1...3]    # [3, 5]
a[3..3]     # [7]
a[-3..-1]   # [5, 7, 9]

배열의 요소에 값을 넣는 것은 []= 연산자를 통해서 이루어집니다.
이미 값이 있더라고 새로운 값으로 채워지고 빈 곳이 생기면 nil로 채워집니다.
a[1..3]     # [3, 5, 7]
a[1...3]    # [3, 5]
a[3..3]     # [7]
a[-3..-1]   # [5, 7, 9]

[시작위치, 갯수] 형식일 경우 우변 값으로 대체됩니다. 갯수가 0이면 삽입이 됩니다.
a = [1, 3, 5, 7, 9]
a[1]    = 'bat'     # [1, "bat", 5, 7, 9]
a[-3]   = 'cat'     # [1, "bat", "cat", 7, 9]
a[3]    = [9, 8]    # [1, "bat", "cat", [9, 8], 9]
a[6]    = 99        # [1, "bat", "cat", [9, 8], 9, nil, 99]

[시작위치..끝위치] 형식으로 채워질 수도 있습니다.
a = [1, 3, 5, 7, 9]
a[2, 2] = 'cat'     # [1, 3, "cat"', 9]
a[2, 0] = 'dog'     # [1, 3, "dog", "cat", 9]
a[1, 1] = [9, 8]    # [1, 9, 8, "dog", "cat", 9]
a[0..2] = []        # ["dog", "cat", 9]
a[5..6] = 99, 98    # ["dog", "cat", 9, nil, nil, 99, 98]

해시(Hash)

해시 형식은 예전에 얘기했었구요. 배열처럼 특별한 형식은 따로 없네요.
장점은 첨자로 정수형만이 아닌 어떤 개체라도 사용할 수 있다는 것이고
단점으로는 배열과는 달리 정렬이 되지 않는다는 것 정도 알고 넘어가겠습니다.
top

tags

Ruby

posted at

2006/08/11 15:02


POST : thought

손수제작물

[우리말 다듬기]‘UCC’순화어 ‘손수제작물’로 결정

지뢰를 없애겠다고 하는 안젤리나 졸리도
고래를 살리겠다고 목숨 거는 그린피스도
우리 말 지키겠다고 하는 국립국어원도 있어야
세상이 좋아지는 방향으로 발전한다고 생각한다.

그래도 갑자기 김치하씨의 역작들이 떠오르면서 피식하게 되는 건 어쩔 수 없는 듯.
top

tags

ucc, 피식

posted at

2006/08/11 14:58


POST : development/ruby

접근 제어, 변수

접근 제어 (Access Control)

대부분의 언어와 마찬가지로 루비도 public, protected, private 세 가지 접근 제어 방식이 있습니다.
class MyClass
        def method1
        end
    public
        def method2
        end
    protected
        def method3
        end
    private
        def method4
        end
end
접근 제어 방식을 지정해 주지 않으면 기본적으로 public입니다.
아래와 같은 형식으로도 사용할 수 있습니다.
class MyClass
    def method1
    end
    def method2
    end
    def method3
    end
    def method4
    end

    public      :method1, :method2
    protected   :method3
    private     :method4
end

변수

일단 코드를 보시죠.
person = "fallroot"
puts person.object_id
puts person.class
puts person
21706610
String
fallroot
person이라는 변수는 object_id를 가지고 있고 String 클래스이며 값을 가지고 있습니다.
그렇다면, 루비에서 변수는 개체일까요? 아닙니다!
단지 변수는 개체의 참조값(reference)일 뿐입니다.
person1 = "fallroot"
person2 = person1

person1[0] = "?"

puts person1
puts person2
?allroot
fallroot
결과 화면을 보면 알겠지만 person1과 person2는 고유의 값을 저장하고 있지 않습니다.
person2 = person1 이 문장은 힙(heap)의 어딘가에 있는 문자열의 주소를 복사하게 되는 거죠.
그렇다면 주소가 아닌 문자열 자체를 복사하려면 어떻게 할까요?
최상위 클래스 Object 중에 dup 메소드가 있습니다.
person2 = person1.dup
마지막으로 변수의 값을 변경하지 못하게 하는 freeze 메소드가 있다는 것 참고하세요.
top

tags

Ruby

posted at

2006/08/08 18:36


POST : development/ruby

싱글턴 패턴

디자인 패턴을 공부하셨으면 익히 아실 내용일 거구요.
Java나 JSP에서 흔히 접하는 Connection Pool이 대부분 이 싱글턴 패턴입니다.
싱글턴(Singleton)이란 한글로 번역하자면 독자(獨子)라는 뜻입니다.
클래스의 개체가 오직 하나만 존재하는 경우를 의미하는 것이죠.
클래스 변수와 클래스 메소드를 이용해서 이 싱글턴 패턴을 구현해 보겠습니다.
class MyLogger
    private_class_method :new
    @@logger = nil
    def MyLogger.create
        @@logger = new unless @@logger
        @@logger
    end
end

puts MyLogger.create.id
puts MyLogger.create.id
21705040
21705040
  • private_class_method는 이미 존재하는 메소드를 private 속성으로 바꿔줍니다.
    • 여기선 new 메소드를 직접 사용하지 못하도록 합니다.
  • unless는 if와 조건이 반대인 조건문입니다.
  • object_id를 비교해보면 MyLogger의 개체가 하나만 생성되었다는 것을 알 수 있습니다.

사실 위 코드는 멀티 쓰레딩에서는 싱글턴을 보장할 수 없습니다.
관련 코드를 작성해도 되겠지만 루비에서 제공해주는 라이브러리가 이미 있습니다. 아싸~
require 'singleton'

class MyClass
    include Singleton
end

a = MyClass.instance
b = MyClass.instance

puts a.object_id
puts b.object_id
21675740
21675740
  • require문을 사용하여 singleton.rb를 읽습니다.
  • include문을 사용하여 Singleton 모듈의 메소드를 사용합니다.
  • instance 메소드를 사용하여 개체를 생성합니다.
include문과 모듈에 대한 설명이 필요하겠지만 뒤에 하기로 하겠습니다. ㄴ(-_-ㆀ)ㄱ
top

tags

Ruby

posted at

2006/08/08 13:16


CONTENTS

just backup
BLOG main image
현실㉠ㅓ부
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글 최근 트랙백
카테고리 태그 구름사이트 링크