ฉันต้องการสตริงอักขระแบบสุ่มเท่านั้น (ตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็ก) ไม่มีตัวเลขใน Go วิธีที่เร็วและง่ายที่สุดในการทำเช่นนี้คืออะไร?
ฉันต้องการสตริงอักขระแบบสุ่มเท่านั้น (ตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็ก) ไม่มีตัวเลขใน Go วิธีที่เร็วและง่ายที่สุดในการทำเช่นนี้คืออะไร?
คำตอบ:
ทางออกของ Paulให้วิธีแก้ปัญหาที่ง่ายและทั่วไป
คำถามที่ถามสำหรับ"วิธีที่เร็วและง่ายที่สุด" มาพูดถึงส่วนที่เร็วที่สุดด้วย เราจะมาถึงรหัสสุดท้ายและเร็วที่สุดในลักษณะวนซ้ำ การเปรียบเทียบการทำซ้ำแต่ละครั้งสามารถพบได้ในตอนท้ายของคำตอบ
การแก้ปัญหาและการเปรียบเทียบรหัสที่สามารถพบได้บนไปสนามเด็กเล่น รหัสในสนามเด็กเล่นเป็นไฟล์ทดสอบไม่ใช่ไฟล์เรียกทำงาน คุณต้องบันทึกลงในไฟล์ชื่อXX_test.go
และเรียกใช้ด้วย
go test -bench . -benchmem
คำนำ :
วิธีแก้ปัญหาที่เร็วที่สุดไม่ใช่วิธีแก้ปัญหาหากคุณต้องการสตริงแบบสุ่ม สำหรับวิธีการแก้ปัญหาของพอลที่สมบูรณ์แบบ นี่คือถ้าประสิทธิภาพไม่สำคัญ แม้ว่า 2 ขั้นตอนแรก ( ไบต์และส่วนที่เหลือ ) อาจเป็นการประนีประนอมที่ยอมรับได้: พวกเขาปรับปรุงประสิทธิภาพโดยเช่น 50% (ดูตัวเลขที่แน่นอนในIIส่วนมาตรฐาน ) และพวกเขาไม่เพิ่มความซับซ้อนอย่างมีนัยสำคัญ
ต้องบอกว่าแม้ว่าคุณไม่ต้องการทางออกที่เร็วที่สุดการอ่านคำตอบนี้อาจเป็นการผจญภัยและการศึกษา
เพื่อเป็นการเตือนความทรงจำโซลูชันดั้งเดิมที่เราปรับปรุงอยู่คือ:
func init() {
rand.Seed(time.Now().UnixNano())
}
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func RandStringRunes(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
หากตัวอักษรให้เลือกและรวบรวมสตริงแบบสุ่มมีเพียงตัวอักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็กของตัวอักษรภาษาอังกฤษเราสามารถทำงานกับไบต์เท่านั้นเนื่องจากตัวอักษรภาษาอังกฤษจับคู่กับไบต์ 1 ต่อ 1 ในการเข้ารหัส UTF-8 (ซึ่ง คือวิธีที่ Go เก็บสตริง)
ดังนั้นแทนที่จะ:
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
เราสามารถใช้:
var letters = []bytes("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
หรือดีกว่า:
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
ทีนี้นี่เป็นการปรับปรุงครั้งใหญ่: เราสามารถทำให้มันเป็นconst
(มีstring
ค่าคงที่ แต่ไม่มีค่าคงที่แบบแบ่งย่อย ) ในฐานะที่เป็นกำไรพิเศษการแสดงออกlen(letters)
จะเป็นconst
! (นิพจน์len(s)
เป็นค่าคงที่หากs
เป็นค่าคงที่สตริง)
และราคาเท่าไหร่ ไม่มีอะไรทั้งนั้น. string
s สามารถจัดทำดัชนีซึ่งดัชนีไบต์สมบูรณ์แบบสิ่งที่เราต้องการ
จุดหมายต่อไปของเราจะเป็นดังนี้:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
การแก้ปัญหาก่อนหน้านี้ได้รับการสุ่มหมายเลขที่กำหนดตัวอักษรแบบสุ่มโดยการเรียกrand.Intn()
ซึ่งได้รับมอบหมายให้ซึ่งได้รับมอบหมายให้Rand.Intn()
Rand.Int31n()
นี่จะช้ากว่าเมื่อเปรียบเทียบกับrand.Int63()
ที่สร้างตัวเลขสุ่มด้วย 63 บิตสุ่ม
ดังนั้นเราสามารถเรียกrand.Int63()
และใช้ส่วนที่เหลือได้หลังจากหารด้วยlen(letterBytes)
:
func RandStringBytesRmndr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
}
return string(b)
}
วิธีนี้ใช้ได้ผลและเร็วกว่ามากข้อเสียคือความน่าจะเป็นของตัวอักษรทั้งหมดจะไม่เหมือนกันrand.Int63()
ทุกประการ แม้ว่าการบิดเบือนจะน้อยมากเนื่องจากจำนวนตัวอักษร52
มีขนาดเล็กกว่า1<<63 - 1
มากดังนั้นในทางปฏิบัติมันดีมาก
นี้เพื่อให้เข้าใจได้ง่ายขึ้น: 0..5
สมมติว่าคุณต้องการจำนวนสุ่มในช่วงของ ใช้ 3 บิตสุ่มนี้จะผลิตตัวเลขที่มีความน่าจะเป็นสองเท่ากว่าจากช่วง0..1
2..5
การใช้ 5 บิตสุ่มตัวเลขในช่วง0..1
จะเกิดขึ้นกับ6/32
ความน่าจะเป็นและตัวเลขในช่วงที่2..5
มี5/32
ความน่าจะเป็นซึ่งใกล้เคียงกับความต้องการ การเพิ่มจำนวนบิตทำให้สิ่งนี้มีความสำคัญน้อยลงเมื่อถึง 63 บิตมันมีความสำคัญน้อยมาก
เมื่อสร้างโซลูชันก่อนหน้านี้เราสามารถรักษาการกระจายตัวอักษรที่เท่ากันโดยใช้บิตสุ่มจำนวนต่ำสุดที่มากที่สุดเท่าที่จำเป็นเพื่อแสดงถึงจำนวนตัวอักษร ดังนั้นสำหรับตัวอย่างเช่นถ้าเรามี 52 ตัวอักษรก็ต้อง 6 52 = 110100b
บิตเพื่อเป็นตัวแทนของมัน ดังนั้นเราจะใช้ต่ำสุด 6 rand.Int63()
บิตจำนวนที่ส่งกลับโดย และเพื่อรักษากระจายเท่าเทียมกันของตัวอักษรเราเท่านั้น "ยอมรับ" 0..len(letterBytes)-1
จำนวนถ้ามันตกอยู่ในช่วง หากบิตต่ำสุดสูงกว่าเราจะละทิ้งมันและสอบถามหมายเลขสุ่มใหม่
โปรดทราบว่าโอกาสของบิตต่ำสุดที่จะมากกว่าหรือเท่ากับlen(letterBytes)
น้อยกว่า0.5
โดยทั่วไป ( 0.25
โดยเฉลี่ย) ซึ่งหมายความว่าแม้ว่าจะเป็นกรณีนี้การทำซ้ำกรณี "หายาก" นี้จะลดโอกาสที่จะไม่พบสิ่งที่ดี จำนวน. หลังจากการn
ทำซ้ำโอกาสที่เราจะไม่มีดัชนีที่ดีนั้นมีค่าน้อยกว่าpow(0.5, n)
และนี่เป็นเพียงการประมาณค่าระดับสูง ในกรณีของ 52 ตัวอักษรโอกาสที่ 6 บิตต่ำสุดไม่ดีเป็นเพียง(64-52)/64 = 0.19
; ซึ่งหมายความว่าเช่นว่าโอกาสที่จะได้มีตัวเลขที่ดีหลังจาก 10 1e-8
ซ้ำเป็น
ดังนั้นนี่คือทางออก:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)
func RandStringBytesMask(n int) string {
b := make([]byte, n)
for i := 0; i < n; {
if idx := int(rand.Int63() & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i++
}
}
return string(b)
}
วิธีการแก้ปัญหาก่อนหน้านี้เพียงใช้ต่ำสุด 6 บิต 63 rand.Int63()
บิตแบบสุ่มกลับโดย นี่เป็นเรื่องเสียเพราะการได้บิตสุ่มเป็นส่วนที่ช้าที่สุดของอัลกอริทึมของเรา
หากเรามี 52 ตัวอักษรนั่นหมายถึง 6 บิตรหัสดัชนีตัวอักษร ดังนั้น 63 บิตสุ่มสามารถกำหนด63/6 = 10
ดัชนีตัวอักษรที่แตกต่างกัน มาใช้ 10 ทั้งหมดนี้กัน:
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
func RandStringBytesMaskImpr(n int) string {
b := make([]byte, n)
// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = rand.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
การปรับปรุง Maskingค่อนข้างดีไม่มากที่เราสามารถปรับปรุงได้ เราทำได้ แต่ไม่คุ้มกับความซับซ้อน
ตอนนี้เราจะค้นหาสิ่งอื่นเพื่อปรับปรุง แหล่งที่มาของตัวเลขสุ่ม
มีcrypto/rand
แพ็คเกจที่มีRead(b []byte)
ฟังก์ชั่นดังนั้นเราสามารถใช้มันเพื่อรับจำนวนมากด้วยการโทรเพียงครั้งเดียวเท่าที่เราต้องการ สิ่งนี้จะไม่ช่วยในเรื่องของประสิทธิภาพเนื่องจากcrypto/rand
ใช้ตัวสร้างหมายเลขเทียมแบบเข้ารหัสลับที่ปลอดภัยดังนั้นมันจึงช้ากว่ามาก
งั้นลองทำmath/rand
แพ็คเกจกัน rand.Rand
ใช้rand.Source
เป็นแหล่งที่มาของบิตสุ่ม rand.Source
เป็นอินเทอร์เฟซที่ระบุInt63() int64
วิธีการ: สิ่งเดียวที่เราต้องการและใช้ในโซลูชันล่าสุดของเรา
ดังนั้นเราจึงไม่จำเป็นต้องมีrand.Rand
(อย่างชัดเจนหรือทั่วโลกแบ่งปันหนึ่งในrand
แพคเกจ) a rand.Source
ก็เพียงพอแล้วสำหรับเรา:
var src = rand.NewSource(time.Now().UnixNano())
func RandStringBytesMaskImprSrc(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
โปรดทราบด้วยว่าโซลูชันล่าสุดนี้ไม่ต้องการให้คุณเริ่มต้น (เมล็ด) ทั่วโลกRand
ของmath/rand
แพคเกจเนื่องจากไม่ได้ใช้ (และเราrand.Source
จะเริ่มต้น / เริ่มต้นอย่างเหมาะสม
อีกสิ่งที่ควรทราบที่นี่: package doc of math/rand
states:
แหล่งที่มาเริ่มต้นมีความปลอดภัยสำหรับการใช้งานพร้อมกันหลาย goroutines
ดังนั้นแหล่งที่มาเริ่มต้นจะช้ากว่าSource
ที่อาจจะได้รับโดยrand.NewSource()
เพราะแหล่งที่มาเริ่มต้นที่มีการให้ความปลอดภัยภายใต้พร้อมกันการเข้าถึง / การใช้งานในขณะที่rand.NewSource()
ไม่ได้นำเสนอนี้ (และทำให้Source
กลับโดยจะมีแนวโน้มที่จะเร็ว)
strings.Builder
การแก้ปัญหาก่อนหน้านี้ทั้งหมดกลับstring
มีเนื้อหาที่ถูกสร้างขึ้นเป็นครั้งแรกในชิ้น ( []rune
ในปฐมกาลและ[]byte
ในการแก้ไขปัญหาที่ตามมา) string
และแปลงแล้ว การแปลงครั้งสุดท้ายนี้จะต้องทำสำเนาเนื้อหาของชิ้นเพราะstring
ค่าไม่เปลี่ยนรูปและหากการแปลงจะไม่ทำสำเนาก็ไม่สามารถรับประกันได้ว่าเนื้อหาของสตริงจะไม่แก้ไขผ่านชิ้นเดิม ดูรายละเอียดได้ที่วิธีแปลงสตริง utf8 เป็น [] ไบต์ และgolang [] ไบต์ (สตริง) VS [] ไบต์ (* สตริง)
ไป 1.10 strings.Builder
แนะนำ strings.Builder
รูปแบบใหม่ที่เราสามารถใช้ในการสร้างเนื้อหาของการที่คล้ายกันstring
bytes.Buffer
มันใช้ภายใน[]byte
และเมื่อเราทำเสร็จแล้วเราสามารถรับstring
ค่าสุดท้ายโดยใช้Builder.String()
วิธีการของมัน แต่สิ่งที่เจ๋งคือมันทำสิ่งนี้โดยไม่ทำสำเนาที่เราเพิ่งพูดถึงข้างต้น มันกล้าที่จะทำเช่นนั้นเพราะไบต์ที่ใช้ในการสร้างเนื้อหาของสตริงไม่ถูกเปิดเผยดังนั้นจึงรับประกันได้ว่าไม่มีใครสามารถแก้ไขได้โดยไม่ตั้งใจหรือโดยเจตนาร้ายเพื่อเปลี่ยนสตริงที่
ดังนั้นความคิดต่อไปของเราคือไม่สร้างสตริงแบบสุ่มในชิ้น แต่ด้วยความช่วยเหลือของ a strings.Builder
ดังนั้นเมื่อเราเสร็จแล้วเราสามารถรับและส่งคืนผลลัพธ์โดยไม่ต้องคัดลอก สิ่งนี้อาจช่วยในเรื่องของความเร็วและแน่นอนจะช่วยในเรื่องของการใช้หน่วยความจำและการจัดสรร
func RandStringBytesMaskImprSrcSB(n int) string {
sb := strings.Builder{}
sb.Grow(n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
sb.WriteByte(letterBytes[idx])
i--
}
cache >>= letterIdxBits
remain--
}
return sb.String()
}
โปรดทราบว่าหลังจากสร้างใหม่strings.Buidler
แล้วเราเรียกBuilder.Grow()
วิธีการของมันเพื่อให้แน่ใจว่าได้จัดสรรชิ้นส่วนภายในที่ใหญ่พอ (เพื่อหลีกเลี่ยงการจัดสรรใหม่เมื่อเราเพิ่มตัวอักษรแบบสุ่ม)
strings.Builder
พร้อมแพ็คเกจunsafe
strings.Builder
สร้างสตริงในภายใน[]byte
เช่นเดียวกับที่เราทำเอง โดยทั่วไปแล้วการทำมันผ่านทางstrings.Builder
มีค่าใช้จ่ายบางอย่างสิ่งเดียวที่เราเปลี่ยนไปstrings.Builder
คือการหลีกเลี่ยงการคัดลอกชิ้นสุดท้าย
strings.Builder
หลีกเลี่ยงสำเนาสุดท้ายโดยใช้แพ็คเกจunsafe
:
// String returns the accumulated string.
func (b *Builder) String() string {
return *(*string)(unsafe.Pointer(&b.buf))
}
สิ่งคือเราสามารถทำสิ่งนี้เองได้เช่นกัน ดังนั้นความคิดที่นี่คือการสลับกลับไปที่การสร้างสตริงแบบสุ่มใน a []byte
แต่เมื่อเราทำเสร็จแล้วอย่าแปลงเป็นstring
กลับ แต่ทำการแปลงที่ไม่ปลอดภัย: รับstring
จุดที่ชิ้นของไบต์เป็นข้อมูลสตริง .
นี่คือวิธีที่สามารถทำได้:
func RandStringBytesMaskImprSrcUnsafe(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return *(*string)(unsafe.Pointer(&b))
}
rand.Read()
)ไป 1.7 เพิ่มrand.Read()
ฟังก์ชั่นและRand.Read()
วิธีการ เราควรถูกล่อลวงให้ใช้สิ่งเหล่านี้เพื่ออ่านจำนวนมากเท่าที่เราต้องการในขั้นตอนเดียวเพื่อให้ได้ประสิทธิภาพที่ดีขึ้น
มี "ปัญหา" เล็ก ๆ นี้: เราต้องการจำนวนไบต์กี่ไบต์? เราสามารถพูดได้ว่า: จำนวนตัวอักษรที่ส่งออก เราคิดว่านี่เป็นการประมาณค่าระดับสูงเนื่องจากดัชนีตัวอักษรใช้น้อยกว่า 8 บิต (1 ไบต์) แต่ ณ จุดนี้เรากำลังทำแย่ลงไปอีก (เนื่องจากการสุ่มบิตคือ "ส่วนที่ยาก") และเราได้รับมากกว่าที่ต้องการ
นอกจากนี้โปรดทราบว่าเพื่อรักษาการกระจายตัวของดัชนีจดหมายทั้งหมดอย่างเท่าเทียมกันอาจมีข้อมูลสุ่ม "ขยะ" ที่เราไม่สามารถใช้งานได้ดังนั้นเราจะสิ้นสุดการข้ามข้อมูลบางอย่างและจบลงเมื่อเราผ่านทั้งหมด ชิ้นไบต์ เราจะต้องได้รับเพิ่มเติมสุ่ม bytes "ซ้ำ" และตอนนี้เรากำลังสูญเสียความrand
ได้เปรียบ "การโทรหาแพ็คเกจเดียว" ...
เราสามารถ "ค่อนข้าง" math.Rand()
เพิ่มประสิทธิภาพการใช้งานของข้อมูลแบบสุ่มที่เราได้รับจาก เราอาจประมาณจำนวนไบต์ (บิต) ที่เราต้องการ 1 ตัวอักษรต้องใช้letterIdxBits
บิตและเราต้องการn
ตัวอักษรดังนั้นเราจึงจำเป็นต้องมีn * letterIdxBits / 8.0
ไบต์ปัดเศษขึ้น เราสามารถคำนวณความน่าจะเป็นของดัชนีแบบสุ่มที่ไม่สามารถใช้งานได้ (ดูด้านบน) ดังนั้นเราจึงสามารถร้องขอได้มากขึ้นว่า "มีโอกาสมากขึ้น" ก็เพียงพอแล้ว (หากปรากฎว่าไม่ใช่ เราสามารถประมวลผลชิ้นส่วนของไบต์เป็น "สตรีมบิต" ซึ่งเรามี lib บุคคลที่สามที่ดี: github.com/icza/bitio
(เปิดเผย: ฉันเป็นผู้เขียน)
แต่รหัสเบนช์มาร์กยังคงแสดงว่าเราไม่ชนะ ทำไมถึงเป็นเช่นนั้น?
คำตอบสำหรับคำถามสุดท้ายคือเนื่องจากrand.Read()
ใช้การวนซ้ำและทำการโทรSource.Int63()
ต่อไปเรื่อย ๆ จนกว่ามันจะเติมชิ้นส่วนที่ส่งผ่าน สิ่งที่RandStringBytesMaskImprSrc()
โซลูชันทำได้โดยไม่ต้องบัฟเฟอร์กลางและไม่มีความซับซ้อนเพิ่ม นั่นเป็นเหตุผลที่RandStringBytesMaskImprSrc()
ยังคงอยู่บนบัลลังก์ ใช่RandStringBytesMaskImprSrc()
ใช้สิ่งที่rand.Source
ไม่ซิงโครไนrand.Read()
ซ์ แต่เหตุผลยังคงใช้; และสิ่งนี้พิสูจน์ได้ว่าเราใช้Rand.Read()
แทนrand.Read()
(แบบเดิมไม่ซิงโครไนซ์ด้วย)
เอาล่ะถึงเวลาสำหรับการเปรียบเทียบโซลูชันที่แตกต่างกันแล้ว
ช่วงเวลาของความจริง:
BenchmarkRunes-4 2000000 723 ns/op 96 B/op 2 allocs/op
BenchmarkBytes-4 3000000 550 ns/op 32 B/op 2 allocs/op
BenchmarkBytesRmndr-4 3000000 438 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMask-4 3000000 534 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImpr-4 10000000 176 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrc-4 10000000 139 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrcSB-4 10000000 134 ns/op 16 B/op 1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4 10000000 115 ns/op 16 B/op 1 allocs/op
เพียงแค่เปลี่ยนจากอักษรรูนไบต์เราได้ทันทีมี24%กำไรจากผลการดำเนินงานและความต้องการหน่วยความจำลดลงถึงหนึ่งในสาม
การกำจัดrand.Intn()
และการใช้งานrand.Int63()
จะเพิ่มพลังให้อีก20%
การปิดบัง (และการทำซ้ำในกรณีของดัชนีขนาดใหญ่) ทำให้ช้าลงเล็กน้อย (เนื่องจากการโทรซ้ำ): -22% ...
แต่เมื่อเราทำให้การใช้งานทั้งหมด (หรือมากที่สุด) ของบิต 63 สุ่ม (10 ดัชนีจากที่หนึ่งrand.Int63()
โทร): ความเร็วที่เพิ่มขึ้นครั้งใหญ่: 3 ครั้ง
หากเราชำระด้วย (ไม่ใช่ค่าเริ่มต้นใหม่) rand.Source
แทนrand.Rand
เราจะได้รับ21%อีกครั้ง
ถ้าเราใช้strings.Builder
เราได้รับเล็ก ๆ3.5%ในความเร็วแต่เรายังประสบความสำเร็จ50%ในการลดการใช้งานหน่วยความจำและการจัดสรร! เยี่ยมมาก!
สุดท้ายถ้าเรากล้าที่จะใช้แพคเกจunsafe
แทนของstrings.Builder
เราอีกครั้งได้รับความดี14%
เปรียบเทียบสุดท้ายเพื่อแก้ปัญหาเบื้องต้น: RandStringBytesMaskImprSrcUnsafe()
เป็น6.3 เท่าเร็วกว่าRandStringRunes()
ใช้หกหนึ่งหน่วยความจำและครึ่งหนึ่งเป็นไม่กี่จัดสรร ภารกิจเสร็จสมบูรณ์.
rand.Source
ถูกนำมาใช้ วิธีแก้ปัญหาที่ดีกว่าคือการส่งผ่านrand.Source
ไปยังRandStringBytesMaskImprSrc()
ฟังก์ชั่นและวิธีการไม่จำเป็นต้องล็อคและดังนั้นประสิทธิภาพ / ประสิทธิภาพจะไม่ได้รับผลกระทบ แต่ละ goroutine Source
อาจจะมีเป็นของตัวเอง
defer
เมื่อเห็นได้ชัดว่าคุณไม่ต้องการมัน ดูgrokbase.com/t/gg/golang-nuts/158zz5p42w/…
defer
การปลดล็อก mutex ทั้งก่อนหรือหลังการโทรล็อคเป็น IMO ส่วนใหญ่เป็นความคิดที่ดีมาก คุณรับประกันได้ว่าจะไม่ลืมที่จะปลดล็อค แต่ยังปลดล็อคแม้ในฟังก์ชั่นความตื่นตระหนกที่ไม่ร้ายแรง
คุณสามารถเขียนโค้ดได้ รหัสนี้อาจง่ายขึ้นเล็กน้อยถ้าคุณต้องการใช้ตัวอักษรทั้งหมดเป็นไบต์เดียวเมื่อเข้ารหัสใน UTF-8
package main
import (
"fmt"
"time"
"math/rand"
)
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(randSeq(10))
}
rand.Seed(time.Now().Unix())
หรือrand.Seed(time.Now().UnixNano())
math/rand
; ใช้crypto/rand
(เช่นตัวเลือกของ @ Not_A_Golfer 1) แทน
สองตัวเลือกที่เป็นไปได้ (อาจมีมากกว่า):
ต่อไปนี้icza's
การแก้ปัญหาที่อธิบายไว้อย่างน่าพิศวงและนี่คือการเปลี่ยนแปลงของมันที่ใช้แทนcrypto/rand
math/rand
const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // 52 possibilities
letterIdxBits = 6 // 6 bits to represent 64 possibilities / indexes
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
)
func SecureRandomAlphaString(length int) string {
result := make([]byte, length)
bufferSize := int(float64(length)*1.3)
for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
if j%bufferSize == 0 {
randomBytes = SecureRandomBytes(bufferSize)
}
if idx := int(randomBytes[j%length] & letterIdxMask); idx < len(letterBytes) {
result[i] = letterBytes[idx]
i++
}
}
return string(result)
}
// SecureRandomBytes returns the requested number of bytes using crypto/rand
func SecureRandomBytes(length int) []byte {
var randomBytes = make([]byte, length)
_, err := rand.Read(randomBytes)
if err != nil {
log.Fatal("Unable to generate random bytes")
}
return randomBytes
}
หากคุณต้องการวิธีแก้ปัญหาทั่วไปที่ช่วยให้คุณสามารถส่งต่อในส่วนของอักขระไบต์เพื่อสร้างสตริงออกจากคุณสามารถลองใช้สิ่งนี้:
// SecureRandomString returns a string of the requested length,
// made from the byte characters provided (only ASCII allowed).
// Uses crypto/rand for security. Will panic if len(availableCharBytes) > 256.
func SecureRandomString(availableCharBytes string, length int) string {
// Compute bitMask
availableCharLength := len(availableCharBytes)
if availableCharLength == 0 || availableCharLength > 256 {
panic("availableCharBytes length must be greater than 0 and less than or equal to 256")
}
var bitLength byte
var bitMask byte
for bits := availableCharLength - 1; bits != 0; {
bits = bits >> 1
bitLength++
}
bitMask = 1<<bitLength - 1
// Compute bufferSize
bufferSize := length + length / 3
// Create random string
result := make([]byte, length)
for i, j, randomBytes := 0, 0, []byte{}; i < length; j++ {
if j%bufferSize == 0 {
// Random byte buffer is empty, get a new one
randomBytes = SecureRandomBytes(bufferSize)
}
// Mask bytes to get an index into the character slice
if idx := int(randomBytes[j%length] & bitMask); idx < availableCharLength {
result[i] = availableCharBytes[idx]
i++
}
}
return string(result)
}
หากคุณต้องการที่จะผ่านในแหล่งที่มาของตัวเองของแบบแผนก็จะเป็นที่น่ารำคาญในการปรับเปลี่ยนดังกล่าวข้างต้นจะยอมรับการแทนการใช้io.Reader
crypto/rand
หากคุณต้องการตัวเลขสุ่มแบบเข้ารหัสที่ปลอดภัยและชุดอักขระที่แน่นอนมีความยืดหยุ่น (เช่น base64 ใช้ได้) คุณสามารถคำนวณความยาวของอักขระสุ่มที่คุณต้องการจากขนาดเอาต์พุตที่ต้องการ
ข้อความ 64 ฐานคือ 1/3 ยาวกว่าฐาน 256 (2 ^ 8 กับ 2 ^ 6; 8bits / 6bits = อัตราส่วน 1.333)
import (
"crypto/rand"
"encoding/base64"
"math"
)
func randomBase64String(l int) string {
buff := make([]byte, int(math.Round(float64(l)/float64(1.33333333333))))
rand.Read(buff)
str := base64.RawURLEncoding.EncodeToString(buff)
return str[:l] // strip 1 extra character we get from odd length results
}
หมายเหตุ: คุณยังสามารถใช้ RawStdEncoding หากคุณต้องการ + และ / ตัวอักษรเพื่อ - และ _
หากคุณต้องการฐานสิบหกฐาน 16 นั้นยาวกว่าฐาน 256 2 เท่า (2 ^ 8 เทียบกับ 2 ^ 4; 8bits / 4bits = 2x อัตราส่วน)
import (
"crypto/rand"
"encoding/hex"
"math"
)
func randomBase16String(l int) string {
buff := make([]byte, int(math.Round(float64(l)/2)))
rand.Read(buff)
str := hex.EncodeToString(buff)
return str[:l] // strip 1 extra character we get from odd length results
}
อย่างไรก็ตามคุณสามารถขยายไปยังชุดอักขระใดก็ได้ถ้าคุณมี base256 เป็น baseN encoder สำหรับชุดอักขระของคุณ คุณสามารถทำการคำนวณขนาดเดียวกันกับจำนวนบิตที่ต้องการเพื่อแสดงชุดอักขระของคุณ การคำนวณอัตราส่วน charset โดยพลการใด ๆ ที่เป็น: ratio = 8 / log2(len(charset))
)
แม้ว่าวิธีแก้ปัญหาเหล่านี้ทั้งสองจะมีความปลอดภัยง่าย ๆ ควรรวดเร็วและไม่ทำให้เสียเอนโทรปีของ crypto
นี่คือสนามเด็กเล่นที่แสดงว่าใช้ได้กับทุกขนาด https://play.golang.org/p/i61WUVR8_3Z
func Rand(n int) (str string) {
b := make([]byte, n)
rand.Read(b)
str = fmt.Sprintf("%x", b)
return
}
[]byte
?
นี่คือวิธีของฉัน) ใช้แรนด์คณิตศาสตร์หรือ crypto แรนด์ตามที่คุณต้องการ
func randStr(len int) string {
buff := make([]byte, len)
rand.Read(buff)
str := base64.StdEncoding.EncodeToString(buff)
// Base 64 can be longer than len
return str[:len]
}
หากคุณยินดีที่จะเพิ่มอักขระบางตัวลงในกลุ่มอักขระที่อนุญาตคุณสามารถทำให้โค้ดทำงานกับทุกสิ่งที่มีการสุ่มไบต์ผ่าน io.Reader crypto/rand
ที่นี่เราจะใช้
// len(encodeURL) == 64. This allows (x <= 265) x % 64 to have an even
// distribution.
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
// A helper function create and fill a slice of length n with characters from
// a-zA-Z0-9_-. It panics if there are any problems getting random bytes.
func RandAsciiBytes(n int) []byte {
output := make([]byte, n)
// We will take n bytes, one byte for each character of output.
randomness := make([]byte, n)
// read all random
_, err := rand.Read(randomness)
if err != nil {
panic(err)
}
// fill output
for pos := range output {
// get random item
random := uint8(randomness[pos])
// random % 64
randomPos := random % uint8(len(encodeURL))
// put into output
output[pos] = encodeURL[randomPos]
}
return output
}
random % 64
จำเป็น?
len(encodeURL) == 64
เพราะ หากrandom % 64
ไม่ได้ดำเนินการrandomPos
อาจเป็น> = 64 และทำให้เกินขอบเขตที่ตื่นตระหนกขณะรันไทม์
/*
korzhao
*/
package rand
import (
crand "crypto/rand"
"math/rand"
"sync"
"time"
"unsafe"
)
// 不全局共用rand库,减少锁竞争
type Rand struct {
Seed int64
Pool *sync.Pool
}
var (
MRand = NewRand()
randlist = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
)
// 初始化随机数发生器
func NewRand() *Rand {
p := &sync.Pool{New: func() interface{} {
return rand.New(rand.NewSource(getSeed()))
},
}
mrand := &Rand{
Pool: p,
}
return mrand
}
// 获取种子
func getSeed() int64 {
return time.Now().UnixNano()
}
func (s *Rand) getrand() *rand.Rand {
return s.Pool.Get().(*rand.Rand)
}
func (s *Rand) putrand(r *rand.Rand) {
s.Pool.Put(r)
}
// 获取随机数
func (s *Rand) Intn(n int) int {
r := s.getrand()
defer s.putrand(r)
return r.Intn(n)
}
// 批量获取随机数
func (s *Rand) Read(p []byte) (int, error) {
r := s.getrand()
defer s.putrand(r)
return r.Read(p)
}
func CreateRandomString(len int) string {
b := make([]byte, len)
_, err := MRand.Read(b)
if err != nil {
return ""
}
for i := 0; i < len; i++ {
b[i] = randlist[b[i]%(62)]
}
return *(*string)(unsafe.Pointer(&b))
}
24.0 ns / op 16 B / op 1 allocs /
const (
chars = "0123456789_abcdefghijkl-mnopqrstuvwxyz" //ABCDEFGHIJKLMNOPQRSTUVWXYZ
charsLen = len(chars)
mask = 1<<6 - 1
)
var rng = rand.NewSource(time.Now().UnixNano())
// RandStr 返回指定长度的随机字符串
func RandStr(ln int) string {
/* chars 38个字符
* rng.Int63() 每次产出64bit的随机数,每次我们使用6bit(2^6=64) 可以使用10次
*/
buf := make([]byte, ln)
for idx, cache, remain := ln-1, rng.Int63(), 10; idx >= 0; {
if remain == 0 {
cache, remain = rng.Int63(), 10
}
buf[idx] = chars[int(cache&mask)%charsLen]
cache >>= 6
remain--
idx--
}
return *(*string)(unsafe.Pointer(&buf))
}
BenchmarkRandStr16-8 20000000 68.1 ns / op 16 B / op 1 allocs / op