Symbol
뭔지 모르겠지만 뭔가 있어보이니까 그냥 쓰자
루비를 배워본 적 없이 바로 개발에 투입되다보니 항상 '다들 이렇게 쓰니 나도 그냥 쓰자'라는 마인드였습니다 (a.k.a. 노답마인드).
그럼에도 항상 신경쓰이는 놈이 있었는데 바로 symbol입니다. 그냥 생각없이 쓰자니 눈에 너무 많이 띄더군요. 메소드 명이나 해쉬키로 자주 쓰이니 대충 이런 놈이겠지 싶은 감은 있었지만 정작 누군가 물어봤다면 대답을 못할것같았습니다. '상징이야' 라고 대답하기에는 문과인생을 청산한지도 꽤 오래 됐고 아재처럼 보이기도 싫고, 여튼 그랬어요.
이러한 이유로 첫 TIL 주제를 symbol로 정하게 되었습니다.
자 그렇다면, 심볼이란 뭘까요?
심볼이란 변경불가능한(immutable) 상수입니다.
라는 정의를 매번 봐왔습니다. 그때마다 그런가.. 하고 넘어가곤 했구요. 사실, 심볼이란 무엇인가 보단 그래서 저 심볼을 어디에 어떻게 사용하냐가 더 중요하고 와닿는 질문인 것 같습니다. 그래서 다시 한 번 묻자면,
심볼은 어디에 사용할까요?
대답은 클래스나 메소드명 또는 해쉬의 키값 등에 사용한다 입니다.
메소드명 등에 사용하는 이유는 꽤나 명확합니다. 메소드명은 변경불가능한 고유의 값을 가져야 하기 때문입니다. 문제는 해쉬의 키값에는 왜 사용하냐 입니다.
사실 제가 처음 짠 루비 코드를 보면 해쉬의 키값으로 심볼을 사용했을때도, string을 사용했을 때도 있었습니다. 중구난방이었죠. 심볼의 특성을 제대로 이해못한 채 string이나 그게 그거 아닌가 싶은 마음이었습니다. 만약 사수가 있었다면 제 코드를 보자마자 몇 대 때렸을 것 같아요.
여기서 다시 한 번 심볼의 특성을 설명하자면,
1. 심볼은 불변하다(immutable)
2. 심볼은 메모리에서 같은 영역을 참조한다. 즉, 메모리 최적화.
str1 = "test"
str2 = "test"
p str1.object_id # 70172287396620
p str2.object_id # 70172287396600
symbol1 = :test
symbol2 = :test
p symbol1.object_id # 371868
p symbol2.object_id # 371868
메모리에서 같은 영역을 참조한다는 점이 꽤 중요한데요,
해쉬테이블의 작동원리를 살펴보자면, 해쉬키를 해쉬함수를 통해 해쉬화하고 그 해쉬화된 키와 밸류를 저장하는 방식인데,
만약 string을 해쉬키로 사용하게된다면, 루비는 필연적으로 그 string을 메모리에서 찾는 과정을 거쳐야 합니다. (같은 문자열로 보일지라도 메모리에서 참조하는 영역이 각자 다르기 때문)
반면 심볼일 경우, 해당 심볼은 모두 같은 영역을 참조하기에 해당 심볼을 해쉬화하는 시간을 단축할 수 있습니다.
해쉬화 벤치마킹 :
count = 100000000
Benchmark.bm do |bm|
bm.report('Symbol:') do
count.times { :symbol.hash }
end
bm.report('String:') do
count.times { "string".hash }
end
end
결과 :
user system total real
Symbol: 5.516562 0.056877 5.573439 ( 5.952674)
String: 10.321092 0.125729 10.446821 ( 11.764312)
이렇듯 심볼을 사용함으로써 메모리 최적화 뿐만 아니라 탐색시간의 단축도 이뤄낼 수 있기 때문에 해쉬의 키로 심볼을 사용하는 것이 코드 측면에서 효율적입니다.
* 참고로, 루비에서 심볼은 심볼테이블로 관리를 합니다. 네임과 내부id가 테이블에 등록되며 이를 통해 좀 더 수월하고 빠르게 탐색이 가능합니다.
마지막으로, 저와 같은 루린이 여러분, 항상 이 문장을 마음에 새겨둡시다.
심볼은 식별자로, string은 데이터로 사용하자!!
# 2020.12.11 추가
루비의 rest-client gem을 이용하여 wordpress api를 불러오는 작업을 하는 도중 reponse의 헤더에 담긴 값이 자꾸만 nil로 오는 현상이 발생했습니다.
response = RestClient::Request.execute(
method: :get,
url: @api_url,
headers: {
content_type: 'application/json'
}
)
p response.headers["x_wp_totalpages"] # nil
뭐 대충 이런 식의 코드였는데요,
nil값이 오는 이유는 바로 해쉬의 키 값이 스트링이 아닌 심볼이었기 때문입니다.
response.headers[:x_wp_totalpages]
이렇게 바꿔주니 작동을 하네요.
reference :
https://stackoverflow.com/questions/8189416/why-use-symbols-as-hash-keys-in-ruby
stackoverflow.com/questions/45131784/how-are-symbols-faster-than-strings-in-hash-lookups
www.rubyguides.com/2018/02/ruby-symbols/