Эх сурвалжийг харах

Soup up base64Wrap, add test and benchmark.

It now uses less memory and is faster.

Previously:
Benchmark_base64Wrap    1000    2177418 ns/op    389576 B/op    2304 allocs/op

Now:
Benchmark_base64Wrap    2000    1282537 ns/op       145 B/op       1 allocs/op
Jed Denlea 11 жил өмнө
parent
commit
701ff9b41a
2 өөрчлөгдсөн 47 нэмэгдсэн , 8 устгасан
  1. 17 8
      email.go
  2. 30 0
      email_test.go

+ 17 - 8
email.go

@@ -232,14 +232,23 @@ func isPrintable(c rune) bool {
 // base64Wrap encodeds the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
 // The output is then written to the specified io.Writer
 func base64Wrap(w io.Writer, b []byte) {
-	encoded := base64.StdEncoding.EncodeToString(b)
-	for i := 0; i < len(encoded); i += MaxLineLength {
-		// Do we need to print 76 characters, or the rest of the string?
-		if len(encoded)-i < MaxLineLength {
-			fmt.Fprintf(w, "%s\r\n", encoded[i:])
-		} else {
-			fmt.Fprintf(w, "%s\r\n", encoded[i:i+MaxLineLength])
-		}
+	// 57 raw bytes per 76-byte base64 line.
+	const maxRaw = 57
+	// Buffer for each line, including trailing CRLF.
+	var buffer [MaxLineLength + len("\r\n")]byte
+	copy(buffer[MaxLineLength:], "\r\n")
+	// Process raw chunks until there's no longer enough to fill a line.
+	for len(b) >= maxRaw {
+		base64.StdEncoding.Encode(buffer[:], b[:maxRaw])
+		w.Write(buffer[:])
+		b = b[maxRaw:]
+	}
+	// Handle the last chunk of bytes.
+	if len(b) > 0 {
+		out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
+		base64.StdEncoding.Encode(out, b)
+		out = append(out, "\r\n"...)
+		w.Write(out)
 	}
 }
 

+ 30 - 0
email_test.go

@@ -3,6 +3,10 @@ package email
 import (
 	"net/smtp"
 	"testing"
+
+	"bytes"
+	"crypto/rand"
+	"io/ioutil"
 )
 
 func TestEmail(*testing.T) {
@@ -32,3 +36,29 @@ func ExampleAttach() {
 	e := NewEmail()
 	e.AttachFile("test.txt")
 }
+
+func Test_base64Wrap(t *testing.T) {
+	file := "I'm a file long enough to force the function to wrap a\n" +
+		"couple of lines, but I stop short of the end of one line and\n" +
+		"have some padding dangling at the end."
+	encoded := "SSdtIGEgZmlsZSBsb25nIGVub3VnaCB0byBmb3JjZSB0aGUgZnVuY3Rpb24gdG8gd3JhcCBhCmNv\r\n" +
+		"dXBsZSBvZiBsaW5lcywgYnV0IEkgc3RvcCBzaG9ydCBvZiB0aGUgZW5kIG9mIG9uZSBsaW5lIGFu\r\n" +
+		"ZApoYXZlIHNvbWUgcGFkZGluZyBkYW5nbGluZyBhdCB0aGUgZW5kLg==\r\n"
+
+	var buf bytes.Buffer
+	base64Wrap(&buf, []byte(file))
+	if !bytes.Equal(buf.Bytes(), []byte(encoded)) {
+		t.Fatalf("Encoded file does not match expected: %#q != %#q", string(buf.Bytes()), encoded)
+	}
+}
+
+func Benchmark_base64Wrap(b *testing.B) {
+	// Reasonable base case; 128K random bytes
+	file := make([]byte, 128*1024)
+	if _, err := rand.Read(file); err != nil {
+		panic(err)
+	}
+	for i := 0; i <= b.N; i++ {
+		base64Wrap(ioutil.Discard, file)
+	}
+}