인자 값으로 특정 STL 컨테이너 반복자(iterator)의 시작 위치와 끝 위치를 넣어 데이터를 담을 수 있다. 예를 들어 a라는 vector 컨테이너의 데이터를 vecArrary라는 또 다른 컨테이너에 전부 넣어 초기화를 하고 싶다면 다음과 같이 처리할 수 있다.
vector<bool> vecArrary( a.begin(), a.end() ); // vecArrary를 a 요소 값으로 담는다.
그렇다면 반복자라는 것은 무엇일까?
반복자 iter가 있다고 가정했을 때, 흔히 컨테이너를 순회하려면 ++iter 와 같이 ++연산자를 사용하여 현재 반복자가 어느 데이터를 가리키고 있는지 알 수 있다. 이처럼 반복자를 증가시켜 현재 가리키고 있는 데이터의 위치를 알 수 있듯이, 1컨테이너 안의 데이터들은 메모리 블록에 차례대로 쌓아놓게 되는 것이다. 즉, 반복자는 특정 컨테이너에서 해당 데이터의 메모리 위치를 가리키게 된다.
마찬가지로 배열 또한 데이터를 메모리 블록에 차례대로 쌓아놓아, 배열의 시작 주소부터 끝 주소까지 넣으면 그 값으로 컨테이너를 생성하게 되는 것이다.
# 정 리
배열과 vecotr 의 데이터는 메모리에 차례대로 위치한다.
vector를 생성할 때 초기화 함수 vector(_Iter _First, _Iter _Last) 의 값으로 데이터의 시작 주소와 끝 주소를 넣어주면 해당 데이터를 vector에 담게 된다. 마찬가지로 동일한 Sequence container인 배열 또한 시작 주소와 끝 주소를 넣어서 데이터를 vector에 담을 수 있다. 배열과 vector의 데이터가 메모리 주소값에 차례대로 있어서 가능한 일이다.
여기서 알 수 있는 사실은 컨테이너에 데이터를 삽입하는 과정은 성능에 영향을 준다는 것이다. 컨테이너의 데이터들이 주소 값을 차례로 가지고 있어서 컨테이너의 최대 사이즈가 변경되면 알맞은 공간에 주소 값을 재할당하여 속도가 저하된다. 그래서 미리 최대 사이즈를 할당해 놓는 reserve() 함수를 사용하곤 한다.
전체 코드의 40% 이상을 루아로 개발한 Adobe사의 Photoshop Lightroom
어도비 포토샵 개발자 Mark Hamburg는 새로운 제품 개발에 루아를 활용하여 쉽고, 강력하며, 높은 성능을 제공할 수 있었다고 전했다. 이 제품이 바로 Photoshop Lightroom이다.뛰어난 스크립트 언어인 루아의 장점도 어떻게 활용하느냐에 따라, 득 또는 실이 될 수도 있다는 점을 반드시 명심해야 한다.
지난번 루아 퍼포먼스 팁(Lua Performance Tip) : 1. 지역변수를 사용하자. 에서는 루아의 지역변수를 사용을 통한 최적화 방법에 대해 알아봤는데, 이번 포스트는 테이블의 초기화와 관련된 최적화를 설명한다. 루아에서 테이블은 일반 변수(문자열, 숫자 등) 못지않게 많이 쓰이며 중요한 역할을 한다. 테이블의 성능과 메모리 관리에 대해서 이해를 하면 퍼포먼스에 좋은 영향을 줄 것이다.
# 내용 요약 테이블은 키와 값을 가지게 되는데, 특정 테이블에 키와 값을 추가하면 테이블의 크기를 계산하여 공간을 할당한다. 흔히 STL 자료형에서 Capacity이 있듯 루아에서도 테이블의 알맞은 크기로 초기 할당을 함으로써, 반복문에서 값을 설정하며 발생하는 오버헤드를 사전에 방지할 수 있다.
아래는 Roberto Ierusalimschy의 Lua Performance Tip 중 About Table 를 번역한 내용이다. 실제 원문은 Lua Programming Gems(http://www.lua.org/gems/sample.pdf)에 수록되어 있다. About Table은 루아에서 많이 쓰이는 테이블의 초기화와 할당에 대한 최적화 방법에 대해서 설명한다.
Lua Performance Tip - 테이블 일반적으로 루아에서 테이블을 사용할 때, 루아 내부에서 테이블을 어떻게 정의하고 있는지 알 필요는 없다. 실제로 루아는 사용자에게 겉으로 드러내놓지 않고 내부에서 테이블을 상세히 정의하고 있다. 그러나 이러한 세부적인 정의를 루아가 처리함으로써, 테이블 연산의 퍼포먼스는 루아 스스로에 의해 좌우하게 된다. 따라서 테이블을 사용하는 프로그램의 최적화(사실상 어떠한 루아 프로그램이라도)를 위해, 루아에서 어떻게 테이블을 정의하고 있는지 조금은 알고 있는 게 나을 것이다.
루아에서 테이블의 정의는 다소 영리한 알고리즘을 포함하고 있다. 루아에서의 모든 테이블은 배열(Array) 파트와 해시(Hash) 파트, 이 2가지로 구성된다. 배열 파트는 1부터 n(특정한 값의 n) 범위의 정수 키를 갖는 모든 값을 저장한다.(앞으로 어떻게 n이 빠르게 계산되는지를 논할 것이다.) 그밖에 모든 값(정수 키 이외의 범위)은 해시 파트에서 처리한다.
이름에서 알 수 있듯이 해시 파트는 키를 저장하거나 찾기 위해 해시 알고리즘을 사용한다. 해시 알고리즘은 공개된 주소 테이블을 호출하여 사용된다. 이것은 모든 값이 스스로 해시 배열에 저장되는걸 의미한다. 해시 함수는 하나의 키를 기본 인덱스로 제공한다. 만약 저장된 키가 서로 충돌(두 개의 키가 같은 주소 값에 뒤엉켜 있을 때) 한다면, 각각의 값이 하나의 배열 공간을 점유하는 방식으로 연결 리스트화 된다.
테이블에 새로운 키를 삽입하려는데 해시 배열이 가득 차게 되면 루아는 리해시(rehash)를 수행한다. 해시 재수행의 첫 번째로 새로운 배열 파트와 해시 파트의 크기를 결정한다. 그럼으로써 루아는 모든 값을 순회하며 카운팅하고 분류하게 된다. 그리고 나서 배열 파트의 값들을 절반 이상 채우며 두 배의 능력으로 배열 파트의 크기를 정한다. 해시 테이블의 크기는 두 배의 가장 작은 능력으로 남은 모든 값을 처리할 수 있게 된다. So, Lua traverses all entries, counting and classifying them, and then chooses as the size of the array part the largest power of 2 such that more than half the elements of the array part are filled. The hash size is then the smallest power of 2 that can accommodate all the remaining entries (배열 파트안에서 실제 크기가 완벽하게 일치하지 않다.)
루아에서 빈 테이블을 생성할 때 배열 파트와 해시 파트는 0의 크기를 가지며, 딱히 할당된 배열 값은 없다. 아래의 코드를 수행했을 때 발생하는 내용을 확인해 보자.
local a = {}
for i = 1, 3 do
a[i] = true
end
위의 코드는 a라는 빈 테이블을 한 개 생성하면서 시작한다. 첫 번째 반복문에서 a[1]=true 로 할당은 리해시를 요청하게 된다. 루아에서는 1부터 테이블의 배열 파트 크기를 설정하며 해시 파트는 비어 있는 상태로 유지하게 시킨다. 두 번째 루프를 돌았을 때 a[2]=true로 할당은 또 다시 리해시를 요청하게 된다. 그래서 테이블의 배열파트는 2의 크기를 가지게 된다. 마지막으로 세 번째 루프를 돌았을 때 또 다시 리해시를 요청하며 배열파트의 크기는 4로 증가한다.
위의 코드와 약간은 유사하지만, 아래의 코드는 테이블의 해시 파트 크기 증가가 제외되었다.
a = {}
a.x = 1; a.y = 2; a.z = 3
큰 테이블을 예로 들자면 이런 초기화에 대한 오버헤드는 전체 생성에 대한 부하를 증가시킨다. 3개의 값이 필요한 테이블에 3번의 리해시를 요구하는 동안에, 100만 개의 값을 가지는 테이블은 단지 20번을 필요로 한다. 그러나 작은 테이블을 수천 개 생성한다면 오버헤드의 조합은 중요해질 수 있다.
이전 버전의 루아는 작은 테이블에 대한 초기화의 오버헤드에서 벗어나려고 슬롯을 약간 초기 할당(내가 정확하게 기억한다면 4번)하여 빈 테이블을 생성하였다. 그러나 이러한 접근방식은 메모리를 낭비하게 된다. 예를 들어 테이블(단지 2개의 값을 가지는 테이블처럼)을 100만 개를 생성하였다고 가정했을 때, 각각의 테이블은 이것이 실제로 필요한 메모리의 두 배를 사용하게 된다. 높은 비용을 지불해야 할 것이다. 이런 까닭에 현재의 루아는 초기 할당된 슬롯 없이 빈 테이블을 생성하도록 만들어진 것이다.
만약 당신이 C로 프로그래밍한다면, 루아 API 함수인 lua_createtable를 사용하여 리해시에서 벗어날 수 있다. lua_State 생성 후에 새로운 테이블의 배열파트 초기 크기와 해시 파트 초기 크기 2개의 인자 값을 받는다.1 새로운 테이블의 적절한 크기를 제공하여, 초기 리해시에서 손쉽게 벗어날 수 있다. 그러나 주의해야 한다. 이것은 루아의 테이블을 리해시 할 때에만 줄일 수 있다. 그래서 만약 당신의 초기화 크기가 요구하는 크기보다 클 때에는 루아가 낭비된 공간을 절대로 올바르게 처리할 수 없을 것이다.
루아로 프로그래밍할 때에는 이러한 초기화에 대한 리해시에서 벗어나 생성을 해야 한다. {true, true, true} 를 작성하였을 때, 루아는 그 테이블이 배열파트로 3개의 슬롯이 필요하다는 것을 미리 알아챈다. 그래서 루아는 해당 크기로 테이블을 생성한다. 유사한 방식으로 {x = 1, y = 2, z = 3} 를 작성하였을 경우, 루아는 해시 파트로 4개의 슬롯을 갖는 테이블을 생성한다. 아래의 예제에 나온 루프문은 2.0초 안에 처리가 된다.
for i = 1, 1000000 do
local a = {}
a[1] = 1; a[2] = 2; a[3] = 3
end
만약 올바른 크기로 테이블을 생성한다면 0.7초로 감소한다.
for i = 1, 1000000 do
local a = {true, true, true}
a[1] = 1; a[2] = 2; a[3] = 3
end
또한, {[1] = true, [2] = true, [3] = true} 처럼 작성한다면 루아는 배열 인덱스를 서술하기에는 주어진 표현(이 경우에는 정수 값)에 대한 판단이 아주 훌륭하지 않다. 그래서 이것은 메모리와 CPU 시간을 낭비하면서 해시 파트로 테이블을 4개의 슬롯으로 생성한다.
테이블의 배열과 해시 파트의 크기는 단순히 테이블이 리해시될때에만 재계산된다. 그래서 테이블이 완전히 가득 차 새로운 값을 추가할 필요가 있을 때에만 발생하게 된다. 결과적으로 만약 테이블의 모든 필드(모든 값을 nil로 설정)를 순회하여 지웠을 때는 이 테이블의 크기가 줄어들지 않는다. 그러나 몇 개의 새로운 값을 추가할 때에는 결국 테이블의 크기를 재설정하게 된다. 대개 이것은 문제가 되지 않는다. 만약 값들을 지우거나 새로 추가할 때, 이 테이블의 크기는 완전하게 남아 있다. 그러나 큰 테이블에서는 값을 지움으로써 당신이 메모리 되찾기를 예측할 수 없다. 테이블 스스로 해제되는 게 낫다.
리해시의 능력에서 다소 훌륭하지 못한 편법으로는 충분한 nil 값을 테이블 안에 넣는 것이다. 아래의 예문을 보자.
a = {}
lim = 10000000
for i = 1, lim do a[i] = i end -- 거대한 테이블을 생성한다.
print(collectgarbage("count")) --> 196626
for i = 1, lim do a[i] = nil end -- 모든 값을 지운다.
print(collectgarbage("count")) --> 196626
for i = lim + 1, 2*lim do a[i] = nil end -- 많은 수의 nil 값을 생성한다.
print(collectgarbage("count")) --> 17
특별한 상황을 제외하곤 이 트릭을 추천하지 않는다. 이 트릭은 속도가 느릴뿐더러 현재 테이블의 크기가 얼마나 많이 필요한지 알기도 어렵다.
아마도 루아가 왜 nil 값을 넣어도 테이블을 줄이지 않는지를 궁금해할 것이다. 첫 번째로 테이블에 값을 추가하는 테스트를 제외한 nil 할당은 모든 할당을 느려지게 만드는 걸 확인하자. 두 번째로 더 중요한 점은 테이블을 순회하는 동안 nil 값을 할당할 수 있다. 다음의 루프를 고려해보자.
for k, v in pairs(t) do
if some_property(v) then
t[k] = nil -- 값을 지운다.
end
end
만약 루아에서 값을 nil로 할당하고서 테이블을 리해시 하면 반복문은 참혹하게 순회할 것이다. 단순히 테이블의 모든 값을 지우길 원한다면 아래처럼 테이블을 순회하는 것이 옳을 것이다.
for k in pairs(t) do
t[k] = nil
end
한 가지 더, 아래와 같은 스마트(smart)한 반복문이 있다.
while true do
local k = next(t)
if not k then break end
t[k] = nil
end
그러나 이와 같은 반복문을 큰 테이블에서 사용할 때 속도가 매우 느리다. 이전 키가 없을 때 next 함수에서는 테이블(랜덤한 순서로)의 첫 번째 값을 반환된다. next 함수는 처음부터 테이블의 배열을 순회하면서 nil이 아닌 값을 찾는다. 반복문에서 테이블의 첫 번째 값을 nil로 설정하는 것처럼, next 함수는 오랜 시간에 걸쳐 첫 번째로 nil이 아닌 값을 찾는다. 결론적으로 pairs를 사용한 루프의 순회는 0.04초가 걸리는 반면, 이 스마트한 루프는 100,000개의 값을 테이블에서 지우는 데 20초가 걸린다.
관련 사이트
챔피언의 귀한 Mark Hamburg, Photo courtesy of PhotoshopNews
루아 프로그래밍 최적화의 기본 Rule #1: Don’t do it. Rule #2: Don’t do it yet. (for experts only)
Roberto Ierusalimschy는 루아 프로그래밍의 가장 기본적인 최적화 방법으로 처리량 줄이기를 강조하고 있다. 스크립트 언어의 특성상 코드의 양, 수행되는 처리량이 비대해 질수록 속도는 떨어질 수밖에 없다.
현재 개발 중인 프로젝트에서 사용되는 루아의 느린 퍼포먼스에 고민하다가 Lua Programming Gems를 발견하였다. 루아의 기본적인 테크닉부터 개발 방법론적인 내용까지 루아의 기본 문법에서 벗어나 조금 더 유연한 작업방식을 알려준다. Gems라는 이름에서 알 수 있듯이 자신의 필요한 부부만 골라서 확인하면 된다.
# 내용 요약 스크립트 언어인 루아를 트리거 시스템이나, 데이터 관리 및 가공, 이벤트 처리 등에 사용할 때 최적화는 반드시 필요하다. 특히 루아 스크립트를 사용하는 코드의 양이 비대해 질수록 최적화 여부에 따라 많게는 3~40% 까지 성능이 향상되지 않을까 한다.
최적화의 첫 번째로 지역변수를 최대한 활용하는 것이 있겠다. 루아는 함수를 호출할 때 매번 레지스터에 할당하여 사용되어서 부하가 발생할 수 있다. 속도를 높이기 위해서는 지역변수 local에 우선 함수를 할당하여 사용하면 할당횟수가 줄어들게 되어 처리속도가 증가하게 된다. for 처럼 루프 문 안에서 함수를 호출할 때에는 미리 local 변수로 해당 함수를 연결하여 사용하자.
아래는 Roberto Ierusalimschy의 Lua Performance Tip 중 Basic facts 를 번역한 내용이다. 실제 원문은 Lua Programming Gems(http://www.lua.org/gems/sample.pdf)에 수록되어 있다. Basic facts는 루아의 가장 흔한 사실 중 하나인 지역변수를 최대한 활용하여 최적화를 수행하는 방법에 대해서 설명한다.
Lua Performance Tip - 기본 사실
특정 코드를 수행하기 전, 루아는 소스 코드를 내부 포맷에 알맞게 해석(프리컴파일)한다. 이 포맷은 CPU에서 처리할 수 있도록 만들어진 기계어와 비슷한 가상기계(virtual machine)에 맞게 계속해서 명령을 내리게 된다. 실제로 루아 내부에서는 거대한 스위치 문을 반복하여 돌면서 각각의 명령어를 하나씩 C 코드로 해석한다.
루아 5.0 버전 이후부터는 레지스터 기반의 가상기계를 사용한다고 이미 어디에선가 읽어 봤을 것이다. 이 가상기계의 "레지스터"는 실제 CPU의 레지스터와 일치하지는 않는다. 왜냐하면, 루아의 레지스터와 CPU의 레지스터는 서로 호환되지 않으며 사용 가능한 레지스터의 개수도 상당히 제한적이기 때문이다. 또한, 루아에서는 레지스터를 제공하기 위해 스택(여러 개의 인덱스로 묶은 배열)을 사용한다. 루아에서 사용되는 함수들은 각각의 활성화된 레코드를 갖는다. 스택 조각처럼 함수를 레지스터에 저장하고 함수들은 각각 자신의 레지스터1를 갖게 된다. 함수들은 대략 250개 이상의 레지스터를 사용한다. 왜냐하면, 각각의 명령문은 레지스터를 참조하기 위해 8비트씩을 갖기 때문이다.
어마어마한 수의 레지스터를 제공함으로써 루아의 프리컴파일러는 모든 지역변수를 레지스터에 저장하게 된다. 그 결과 루아에서 지역 변수로의 접근은 상당히 빠르다. 예를 들어 만약 a와 b라는 지역변수가 있다고 가정하면, 루아에서 a를 정의할 때 a = a + b 가 하나의 단일 명령어로 해석된다.
ADD 0 0 1 (a와 b를 레지스터 안에서 각각 0 과 1로 가정했을 때).
반대로, 만약 a 와 b 둘 다 전부 전역변수라고 가정하면 추가적인 코드가 발생하게 된다.
GETGLOBAL 0 0 ; a GETGLOBAL 1 1 ; b ADD 0 0 1 SETGLOBAL 0 0 ; a
결론적으로 루아 프로그램의 성능향상을 위한 가장 중요한 규칙 중 하나를 정의하자: 지역 선언을 사용하자!
만약 프로그램의 현재 성능을 뛰어넘어 퍼포먼스를 높일 필요가 있다면 명확한 한 개(역자주: 전역변수)를 사용하는 것보다 지역변수를 몇몇 장소에서 사용하도록 한다. 만약, 긴 반복문 안에서 특정 함수를 호출하려 한다면 해당 함수를 지역변수에 할당하자.
예를들면 아래와 같다.
for i = 1, 1000000 do
local x = math.sin(i)
end
아래 코드는 위의 코드보다 30% 정도 빠르다.
local sin = math.sin
for i = 1, 1000000 do
local x = sin(i)
end
함수 밖의 지역변수 접근은 함수 내부의 지역변수 접근만큼 빠르지는 않지만, 전역변수에 접근하는 것보다는 여전히 빠르다. 다음 구문을 고려해 보자.
function foo (x)
for i = 1, 1000000 do
x = x + math.sin(i)
end
return x
end
print(foo(10))
foo 함수 밖에 sin 변수를 하나 선언하여 최적화를 할 수 있다.
local sin = math.sin
function foo (x)
for i = 1, 1000000 do
x = x + sin(i)
end
return x
end
print(foo(10))
이 두 번째 코드는 첫 번째 코드보다 30% 정도 빠르게 돌아간다.
루아의 컴파일러는 다른 언어의 컴파일러와 비교해서 꽤 효과적인 반면, 결과물은 무겁게 작동하게 된다. 될 수 있으면 반드시 프로그램에서의 코드 컴파일은 피해야 한다.(예를 들어 loadstring 함수, 역자주: C 함수를 호출하는 바인딩 함수 등) 실제로 동적인 코드를 꼭 실행해야 할 때를 제외하고는 최종 사용자에 의해 돌아가게 되는 코드는 동적인 코드가 컴파일되도록 하지 말아야 한다.
예를 들어, 1부터 100,000까지의 상수 값을 반환하는 함수와 테이블을 생성하는 코드를 살펴보자.
local lim = 10000
local a = {}
for i = 1, lim do
a[i] = loadstring(string.format("return %d", i))
end
print(a[10]()) --> 10
이 코드를 수행하는 데 1.4초가 걸린다.
마지막에서 동적인 컴파일에서 벗어나서 아래 코드는 위와 같이 100,000 개의 함수를 생성한다. 기존보다 1/10초(0.14초)를 단축하게 된다.
function fk (k)
return function () return k end
end
local lim = 100000
local a = {}
for i = 1, lim do a[i] = fk(i) end
print(a[10]()) --> 10
구글 크롬(Google Chrome)은 작업량을 줄여서
똑똑하게 업무를 처리한다. 사람들이 의도하는 데로 업무를 처리하기 위해서 크롬은 검색 박스와 주소 바가
통합되어 있다. 특정 사이트를 방문하는 일반적인 웹 유저라면 누구나 중요하게 생각하는 것을 크롬이 강력하게 제공해준다. 이것은 사용자 식별 ID(Identity)를 브라우저에 포함시킨다는 것이다. 내 말을
전적으로 믿지는 말아라. 단, 금주에 올라온 다음의 기사를
확인하길 바란다. 리드라이트웹(ReadWriteWeb)은
“온라인익명의끝”에 대해서 전했고, 테크
크런치(TechCrunch)는 페이스북 컨넥트(Facebook
Connect)에 관한 “소셜네트워크를위한끝나지않은전쟁:
오픈웹공간에서의당신의신원및개인정보”에
대해 이야기 했다.
특정 사이트에 로그인하여 자신이 좋아하는걸 저장하고 프로필을 생성하는 활동처럼 웹 2.0이 뿌리를 내려 유비쿼터스로 된다. 단지 뉴스나 블로그 보는
것을 넘어 로그인을 하지 않거나 로그인할 자격 없이 특정 사이트에 머무는 건 상당히 보기 드문 행동이다. 이로
인해 거의 모든 사이트가 당신에게 새로운 계정 생성을 요구하며, 쿠키(cookies)로
로그인 상태를 유지시키는 문제점을 가지고 있다. 그래서 쿠키를 잃어버리면 다시 로그인을 해야 한다. 아마
브라우저 내의 패스워드 매니저는 이 고민을 줄여줄 것이다. 그러나 만약 브라우저가 갖고 있는 모든 패스워드를
전부 분실한다면(또는 친구의 컴퓨터를 사용했다면) 누구든지
간에 심한 곤경에 빠지게 될 것이다.
만약 현재와 같은 패스워드에서 벗어나 OpenID를 사용한다면, OpenID로 이용 가능한 사이트(현재 25,000 개 이상의 웹 사이트) 를 방문할 수 있다는 점을 알게 될 것이다. 이런 사이트에 가면 OpenID를 입력하여 접속하는걸 선호할
것이다. 나의 경우 맵퀘스트(MapQuest)에서 내 OpenID인 http://www.davidrecordon.com/ 를
입력하여 로그인을 하거나 Ma.gnolia에
가서 “야후 ID로 로그인(Sign
up with a Yahoo! ID")” 버튼을 누를 것이다. 이러한 상호작용은
여기저기에 다양한 조작(tweaks)으로 오늘날 매우 많이 발생한다.
만약 실제로 OpenID가 주류로서 사용되길 희망한다면,
반드시 변화가 필요하다.
웹
브라우저가 당신이 실제로 어느 웹 사이트를 방문하는지 알고 있다고 생각해 보자.
컴퓨터에 로그인 하는 것처럼 브라우저를 가동시킨다고 가정해보면, 브라우저는 당신에게 “안녕 데이브(역자주:여기서
데이브는 필자)”라 말하고 “암호를 해제하여라” 라고 물어볼 것이다.(크리스메시나는
이 방법에 대한 나의 생각에 상당히 호감을 보였다.) OP(OpenID 공급자, OpenID Provider)와 그 이외의 것들을 통해 안전하게 로그인 함으로서, 브라우저로 웹을 탐험할 때처럼 OpenID를 사용하여 직접 진행해야
하는 특정 사이트에 로그인을 자동으로 할 수 있고 새로운 웹사이트의 계정등록을 도와준다. OpenID를
활용함에 있어서 보다 더 충족할만한 요구사항에 대해 많은 논쟁을 해야 한다. 나는 사람들이 자신의 ID를 쉽게 만들고 관리하며 사용하는 게 온라인에서는 안 좋은 행동이라며 반대할 수는 없다고 생각한다.
한달 조금 넘게 개최된 a
great summit on OpenID usability에서는 현재의
OpenID 상호작용이 어떻게 변할 것인가에 대한 많은 안건이 나왔다.원 클릭 버튼 이상이 되어야 할지, 더 적어야 할지, 큰 로고를 사용해야 할지, 이메일 주소를 채택해야 할지… 나는 OpenID가
실제로 브라우저에 포함되어 제공되어야 한다는 것도 중요하다고 생각한다. 이점은 새로운 발상이
아니다. 주요 브라우저들은 모두 패스워드를 기억하는 기능을 가지고 있다. 심지어 파이어폭스는 이런 성가신 사용자 프로파일을 각각 가지고 있어서 이론상으로는 사람들이 서로 다른 쿠키와
즐겨찾기, 그리고 서로 다른 설정 정보를 가질 수 있다.
ID로 인터넷 공간에서 수행되는 다음의 내용들도 새로운
발상은 아니다. 인포메이션 카드(Information
Cards , 윈도우에서 수행되는 마이크로소프트의 카드스페이스(CardSpace)로 더 많이 알려졌다)는 WS-*와 SAML를 사용하여 리치 데스크톱으로 통합된 신용카드를 가진다. 캐나다에 있을 때 딕 하츠(Dick Hardt)와 공동으로 파이어폭스에서 OpenID와 일반적인 웹 양식 둘 다 잘 사용할 수 있는 스키퍼(Sxipper)를
만들었다. 내가 베리사인(VeriSign)에서
일할 때에는, OpenID를 쉽게 만들고 OpenID를 사용하는 사이트와 OP(OpenID
provider)에서의 피싱을 예방하도록 이때도 역시 파이어폭스 확장기능으로 설계된 OpenID
Seatbelt라는 걸 개발했다.
오늘날,마이스페이스(MySpace),
플락(Flock), 비드웁(Vidoop)은
플락(Flock)에 OpenID로이러한비전을이행하는프로토타입을출시했다.플락 브라우저 플러그인 3개는
OpenID를 관리하도록 도와주고 OpenID가 활성화된 사이트에 있을 때를 검출하여 쉽게
로그인 하도록 만든다. 스키퍼(Sxipper)를 이용하는
주요 유저들이 사용하면서 느끼는 희망하거나 바라는걸 나에게 알려달라.
플락(Flock)의
OpenID는 미리 브라우저 내에 OpenID를 입력하도록 접목시킨 에드온이다. 유저 경험과 그래픽은 베리사인(VeriSign)에서 생성하는 것보다
꽤 적어 피싱(피싱 사이트처럼 보이는 것과 실제로 OP를
통해 로그인하는 걸 확신한다.)을 막기에는 부족해 보이기도 하지만 비드웁(Vidoop)에 포함되어 있는 건 조금 놀랍다. 이처럼 스키퍼(Sxipper)나 베리사인의 OpenID SeatBelt와는 다르게
IDIB(Identity
in the Browser)를하나의프로젝트로갖는플락의
OpenID는 오픈소스라고 할 수 있다. IDIB는
오픈소스이며 이미 플락 에드온으로 만들어져서 제공되고 있다. 나는
IDIB가 파이어폭스에 포함될 것을 확신하며 다른 에드온과 비교되도록 더 많은 커뮤니티 제공이 있을 것이다.
이제 어떻게 할 것인가? 나는 훌륭한 브라우저 플러그인이
어떻게 만들어 지는지 알 수 없으며, 이제 여기서 끝마치려 한다. 브라우저
벤더들이 혁신과 실험성으로 그들의 제품에 ID를 제공함으로써 오픈소스의 노력에 일환으로 Flock이 나아가는 방향은 훌륭해 보인다. 아마도 이것은 다른 브라우저 벤더들이 그들의 다음버전에서는 웹 상에서 신원정보를 쉽고 안전하게 관리할
수 있도록 진지하게 생각하도록 야기될 것이다.
내 생각에 기어스(Gears)는
우리에게 도움을 제공해 줄 것이다.구글의 프로젝트로 시작했을 때 오프라인 지원의 필요성을 포함하여 웹 브라우저를 빠르게 발전시키며, 현재 돌아오는 HTML 5와 새로운 지오로케이션 API(Geolocation API)으로
오프라인을 제공함으로써 성장하였다. 오늘날 기어스는 파이어폭스, 인터넷
익스플로어, 사파리, 크롬,
안드로이드(Android )의 몇 개의 서로 다른 브라우저 및 여러 플랫폼에 사용된다. 오픈소스를 만드는 개발 플랫폼이 어떻게 OpenID를 지원할지에
대한 브라우저의 대처가 이루어지지 않는 한 기어스가 대안이 된다. 단순히 적은 코드를 작성하여 다양한
브라우저에 작동되도록 한다는 의미뿐만 아니라, 이상적으로 충분히 성장한다면 아마 기어스 팀은 가능한
훌륭하게 OpenID를 지원해줄 것이다. 어떤 상황이 발생하던기어스 커뮤니티가 선봉에 서서 오픈소스 예제를 활용한 웹 브라우저 플러그인으로 손실을 최소화 해줄 것이다.
어떻게 생각하나? 사용자 식별 ID(Identity)는 기억장소처럼 브라우저의 필수요소가 되어야 한다는 점에 동의하는가? 몇 개의 잠금 아이콘만으로 의존하여 보안과 같은 이슈에 벗어나 우리 스스로를 만족시킬 것인가, 아니면 괄목할 만한 중계 도구를 받아 들여 중요한 이슈를 표준 셋으로 사용한 거대한 피싱 경고체계(Big-Red-Phishing-Warnings)를 가질 것인가? 실제로
이러한 표준 셋을 통해 매일매일 당신의 웹 사용에 있어서 브라우저가 더 능동적으로 활용될 것 같지 않나?
STL Map의 [ ] 연산자 사용시 주의점STL Map은 시퀀스 컨테이너(Sequence container)처럼 [ ] 연산자를 제공합니다. 중요한 점은 "C++ Standard Library 튜토리얼·레퍼런스(인포북, 니콜라이 M. 조슈티스)" 책에도 나와 있듯이 [ ] 연산자의 인덱스는 정수 값이 아니라 map의 key값이라는 것입니다. 이것을 정확하게 숙지하지 않고 잘못사용하면, 심각한 문제가 생길 수 있습니다. 아래는 [ ] 연산자를 잘못 사용한 코드의 예입니다.
// 커피 자판기를 하나 만들고, 판매할 커피를 추가합니다.
std::map<int, std::string> mapCoffeeMachine;
mapCoffeeMachine[0] = "Espresso";
mapCoffeeMachine[1] = "Macchiato";
mapCoffeeMachine[2] = "ConPana";
mapCoffeeMachine[3] = "Latte";
mapCoffeeMachine[4] = "Cappuccino";
mapCoffeeMachine[5] = "Mcha";
mapCoffeeMachine[6] = "Caramel";
mapCoffeeMachine[7] = "Americano";
// 생각을 해보니 라떼와 카푸치노는 제고가 없네요.
// 라떼와 카푸치노 버튼을 자판기에서 사용하지 않습니다.
mapCoffeeMachine.erase( 3 );
mapCoffeeMachine.erase( 4 );
...
// 한 구매자가 자판기 앞에서 돈을 넣고 아메리카노를 누릅니다.
for ( int i = 0; i < mapCoffeeMachine.size(); i++ )
{
if ( mapCoffeeMachine[i] == "Americano" )
{
... // 따끈따끈한 아메리카노 한잔이 나옵니다.!?
}
}
과연 구매자는 자판기에서 따뜻한 아메리카노 한잔을 뽑아 담배와 함께 커피한잔을 할 수 있을까요? 평생을 기다려도 아메리카노는 나오지 않습니다. 위의 코드는 [ ] 연산자의 인덱스를 정수 값으로 취급한 상당히 잘못된 코드입니다. 문제점은 크게 두 가지로 나뉠 수 있는데, 첫 번째는 size() 함수로 컨테이너의 크기를 구하면 제거된 3, 4번 키 값 때문에 사이즈가 6 이 됩니다. 그러므로 6, 7번 key 값은 아예 비교 대상이 안 됩니다. 커피를 마시러 온 사람은 메뉴를 보고 눌렀는데, 커피는 않나오고 동전이 반환된 꼴이 된 것입니다. 그러나 커피가 않나오는 것보다 더 큰 문제점이 있습니다. 바로 제거한 3, 4번 키 값의 내용이 == 동등 연산자를 통해 빈 value 값이 다시 추가가 된다는 점입니다. 컨테이너의 크기는 8이 되어 이제 커피메뉴에는 8개로 등장하는데, 아무것도 없는 메뉴가 자판기에서 눌러질 수 있다는 사실입니다. 커피가 들어있지 않는 빈 컵만 받게 되겠지요.
위에는 단순히 자판기로만 예를 들었지만 삭제한 내용이 다시 추가되는 문제점은 실제 개발 시에 큰 문제점을 가져올 수 있습니다. 특히 여러 명의 개발자가 작업을 할 경우 개발자 마다 서로 다른 작업방식으로 프로그램이 다운될 수도 있습니다. 이러한 문제점 때문에 컨테이너를 순회할 때는 반드시 반복자를 사용하는 게 필요합니다.