rubyzip/rubyzip — pull request #130 by srawlins (2014-02-04 19:02:40+00:00)
Title: Allocate less Arrays and Strings when writing a zip

I used the [AllocationStats gem](https://github.com/srawlins/allocation_stats) to look at allocations that rubyzip makes while building a zipfile from 1000 small PNG files, and found a few lines that allocated some Strings or Arrays unnecesarily. Here are all the file/line combos that allocate >= 3000 objects:

```
sourcefile sourceline class count
--------------------------------------------------------- ---------- --------------- -----
/Users/srawlins/code/rubyzip/lib/zip/extra_field.rb 63 Array 27000
/Users/srawlins/code/rubyzip/lib/zip/entry_set.rb 80 String 10050
/Users/srawlins/code/rubyzip/lib/zip/extra_field.rb 67 String 7000
/Users/srawlins/code/rubyzip/lib/zip/extra_field.rb 67 Array 7000
/Users/srawlins/code/rubyzip/lib/zip/entry.rb 365 String 5044
/Users/srawlins/code/rubyzip/lib/zip/entry.rb 265 String 4000
/Users/srawlins/code/rubyzip/lib/zip/entry_set.rb 21 String 3840
/Users/srawlins/code/rubyzip/lib/zip/entry.rb 107 String 3000
...
```

Of course, most of these allocations are _not_ unnecessary; only a few are. Many involve simple Strings that are allocated every time a method is called (like [`lib/zip/entry.rb:107`](https://github.com/rubyzip/rubyzip/blob/master/lib/zip/entry.rb#L107)). But this pull request does reduce the top file/line combos to:

```
sourcefile sourceline class count
--------------------------------------------------------- ---------- --------------- -----
/Users/srawlins/code/rubyzip/lib/zip/entry_set.rb 80 String 10040
/Users/srawlins/code/rubyzip/lib/zip/extra_field.rb 63 Array 9000
/Users/srawlins/code/rubyzip/lib/zip/extra_field.rb 69 String 7000
/Users/srawlins/code/rubyzip/lib/zip/entry.rb 365 String 5044
/Users/srawlins/code/rubyzip/lib/zip/entry_set.rb 21 String 4000
/Users/srawlins/code/rubyzip/lib/zip/entry.rb 265 String 4000
/Users/srawlins/code/rubyzip/lib/zip/entry.rb 107 String 3000
...
```

The rubyzip 1.1.0 tag is actually much worse than master, but I could not pin down where the reductions came from. Most of the top offenders in 1.1.0 are in `lib/zip/extra_field[,/generic].rb`.

There is also a noted reduction in memory usage and GC time between master and this PR:

```
# master, three iterations
Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms)
2 1.138 19951080 37552320 938808 85.61200000000002
2 1.091 19951160 37552320 938808 85.92599999999994
2 1.089 19951280 37552320 938808 84.17499999999988

# this PR, three iterations
Index Invoke Time(sec) Use Size(byte) Total Size(byte) Total Object GC Time(ms)
2 0.958 17094520 30192000 754800 70.07600000000013
2 0.926 17094600 30192000 754800 68.41600000000002
2 0.963 17094640 30192000 754800 66.39499999999998
```

Other than that, tests pass.

Additionally, freezing string literals will reduce allocations in Ruby 2.1.0, and shouldn't reduce performance in Ruby < 2.1.0. These basically consist of:

* [`lib/zip/entry.rb`](https://github.com/rubyzip/rubyzip/blob/master/lib/zip/entry.rb) lines 48, 62, 63, 107, 254, 265, 365, 434, ...
* [`lib/zip/entry_set.rb`](https://github.com/rubyzip/rubyzip/blob/master/lib/zip/entry_set.rb) line 80
* [`lib/zip/extra_field.rb`](https://github.com/rubyzip/rubyzip/blob/master/lib/zip/extra_field.rb) lines 69, 75

Would you consider a pull request with lots of `.freeze` added everywhere?