Tutorial

String Interning ของเล่นลับ ที่หลายคนไม่รู้มาก่อน

By Arnon Puitrakul - 20 กันยายน 2021 - 1 min read min(s)

String Interning ของเล่นลับ ที่หลายคนไม่รู้มาก่อน

ก่อนหน้านี้เราอ่านเรื่องความแตกต่างระหว่าง is และเครื่องหมายเท่ากับในการเทียบค่ามา แล้วบังเอิญตอนเล่นบ้า ๆ อยู่ก็เอ๊ะ เจอพฤติกรรมแปลก ๆ ของ Python อยู่ ไปนั่งอ่านไปมา เขาเรียกว่า String Interning มันเป็นของที่ทำให้ Code ของเราเร็วขึ้นมาก ๆ โดยเฉพาะ ถ้าโปรแกรมของเรามีการเทียบ String จำนวนเยอะ ๆ มันเป็นยังไงไปลองดูกัน

Behaviour

ตอนที่เราเจอเรื่องนี้โดยบังเอิญ เราทำการเทียบ String 2 ตัวที่เหมือนกัน ลองดูใน Code ด้านล่าง

>>> a = "Me"
>>> b = "Me"

>>> a == b
True

>>> a is b
True

เราเทียบผ่านทั้งเครื่องหมายเท่ากับ และ is ไปด้วยกันเลย เรารู้อยู่แล้วว่า ทั้ง 2 String นี้มีค่าเท่ากัน การใช้เท่ากับ เป็นการเปรียบเทียบค่าของตัวแปรหรือ Object นั้น ๆ ทำให้ การที่เราใส่เครื่องหมายเท่ากับ แล้วมันออกมาเป็นจริง อันนี้ Make Sense แน่นอน แต่ ที่มันไม่ Make Sense มาก ๆ คือ เมื่อเราบอกว่า a is b ถ้าเราไปอ่านคำสั่ง is มา เราจะรู้ว่า มันไม่ได้เทียบค่าเลย แต่มันเทียบถึง Memory Address เลย

แปลว่า ถ้ามันได้ จริง ออกมา นั่นแปลว่า a และ b มันเป็นของชิ้นเดียวกันเลยเหรอ (ไม่ใช่แค่ค่านะ แต่มันถูกบันทึกอยู่ใน Memory Address เดียวกันเลย) ไหงงั้นละ เพราะเราไม่ได้บอกว่า b = a นิ แต่มันเท่ากันแบบ งง ๆ ลองดูอีกเคสแล้วจะ งง กว่าเดิมอีก

>>> c = "albert einstein"
>>> d = "albert einstein"

>>> c == d
True

>>> c is d
False

งั้นเราลองทำเหมือนเดิมเลยละกัน แต่กับ String ที่ยาวขึ้นอีก ลองใช้เท่ากับ อื้ม เหมือนกัน อันนี้ Make Sense แน่นอน แต่พอใช้ is เท่านั้นแหละ อ้าว อิหยังว้าาาา เมื่อกี้ยังเท่ากันอยู่เลย ทำไมเคสนี้มันไม่เท่าแล้วละ ไหงงั้น นัง Python แกทำให้ชั้นดูแย่ว์

String Interning

พฤติกรรมที่เราลองให้ดูเมื่อครู่ เราเรียกมันว่า String Interning มันเป็นพฤติกรรมของ Python ที่มันจะเลือกเก็บแค่ Copy ของ String อันเดียวลงไปใน Memory เพื่อ ลดการใช้ Memory ลงได้ สมมุติว่า เรามี คำว่า Me สัก 200 ตัวแปร ถ้าเราต้องเก็บก็จะใช้ทั้งหมด 400 Bytes เฉพาะตัวข้อมูล ไม่นับส่วนการเก็บ Addressing ต่าง ๆ แต่ถ้าเราใช้ String Interning เราก็จะใช้แค่ 2 Byte เท่านั้น เพราะมันเก็บแค่ Copy เดียว ลดการใช้ Memory ไปได้มหาศาลเลย

อีกเหตุผลที่น่าสนใจมาก ๆ คือ การทำ String Comparation หรือการเทียบ String นั่นเอง ปกติแล้วเวลาเราจะเปรียบเทียบค่าของ String จริง ๆ เราจะต้อง Loop เข้าไปใน String ทั้ง 2 ตัวเลย แล้วถามว่า อักษรเดียวกันมั้ย แบบนี้ไปเรื่อย ๆ จนกว่าจะสุด String ใดสักตัว ถ้าเท่ากันหมด และ สุดของ String ทั้งสองพร้อม ๆ กัน ก็แปรว่า String นั้นแหละ มันค่าเท่ากัน ซึ่งการทำแบบนี้มันกินเวลามาก ๆ ตัว String Interning เลยบอกว่า งั้นถ้ามันเท่ากันจริง ๆ เราก็เก็บไว้ Copy เดียวเลย แปลว่า มันก็จะชี้ไปในที่ ๆ เดียวกันใน Memory ทำให้เวลาเราเทียบจริง ๆ เราก็เช็คจาก Address ได้เลย ทำให้มันเร็วขึ้นนั่นเอง

อ่านมาถึงขนาดนี้ เอ๊ะอะไรบ้างมั้ย การทำแบบนี้มันมีช่องโหว่อยู่ ถ้าเราบอกว่า มันเก็บ Copy เดียว นั่นแปลว่า ณ ตอนที่เราจะ Assign ค่าในตัวแปร มันจะต้องเข้าไปเช็คก่อนว่า ใน Variable มันมีอะไรบ้างที่เหมือนกัน มันไม่น้อยเลยนะ และใช้เวลาเยอะมาก ๆ โดยเฉพาะ ถ้า String เรายาวมาก ๆ การใช้ Intern ในเคสนี้อาจจะทำให้ชิบหายกันได้ ดังนั้นใน Python มันเลยจำกัดความยาวของ String ที่จะทำ String Interning โดยอัตโนมัติไว้เอง

แหกกฏกันดีกว่า

import sys

>>> c = sys.intern("albert einstein")
>>> d = sys.intern("albert einstein")

>>> c == d
True

>>> c is d
True

แน่นอนว่า มนุษย์ที่หาทำอย่างเราก็อยากจะหาทำไปเรื่อย และ Python ก็ตอบสนองความหาทำได้เป็นอย่างดี เพราะมันมี Function รองรับเพื่อให้เราสามารถทำ String Interning ได้เลย ผ่าน Built-in Module sys ใน Function ที่ชื่อว่า intern() ตามชื่อของสิ่งที่เราต้องการเลย จะเห็นได้ว่า เราใช้ String ที่ยาว ตอนแรกถ้าเราใช้ is มันจะให้เป็น False มา แต่ตอนนี้เรา Force Intern เลย ทำให้เราเทียบ c is d มันเลยได้ True ออกมานั่นเอง

สรุป

String Interning เป็นพฤติกรรมที่น่าสนใจมาก ๆ ใน Python ที่มันเลือกเก็บ String บางตัวไว้ Copy เดียว เพื่อลดขนาดของ Memory ที่ต้องใช้ และ ทำให้การ Compare หรือการเปรียบเทียบ String เร็วขึ้นได้อีก วันนี้เราก็พาไปดูใน 2 เคสคือ เคสที่ Python รับบทนาง Manage จัดการทำให้เราบน String ที่สั้นหน่อย และ เราก็ยังสามารถบังคับให้ Python ทำบน String ที่มีความยาวได้อยู่เช่นกัน