Renames `config` to `generate-config` and implements a new `config`
command that generates the configuration file based on the current
state instead of printing a static string.
Changes the behavior of `AttrStore.SetAttrs()` to first verify
that the attributes haven't changed using a read-only lock before
obtaining a write lock to update the attributes.
Since the write lock serializes access, the previous insert time
sufferred lock contention when inserting a lot of duplicate
attributes. With the `RWMutex`, reads can be done in parallel so
multiple requests don't block each other.
Below is a simple benchmark showing the performance at 1, 5, & 10
goroutines:
BenchmarkAttrStore_Duplicate 100000 137120 ns/op 8428 B/op 76 allocs/op
BenchmarkAttrStore_Duplicate-5 100000 417459 ns/op 8431 B/op 76 allocs/op
BenchmarkAttrStore_Duplicate-10 100000 139466 ns/op 8433 B/op 76 allocs/op
BenchmarkAttrStore_Duplicate 20000000 703 ns/op 368 B/op 5 allocs/op
BenchmarkAttrStore_Duplicate-5 100000000 213 ns/op 368 B/op 5 allocs/op
BenchmarkAttrStore_Duplicate-10 100000000 223 ns/op 368 B/op 5 allocs/op
in order of the diff:
1. When the start and end of the range fall in the same word, special handling
is needed to "mask" off the beginning and end of the word simultaneously to
avoid counting bits at the beginning or end of the word that aren't in the
range.
2. `i++` is needed at the end of the first partial word to avoid counting this
word in the next block.
3. the shift amount for right shifts is 64 - (end % 64) rather than just end
% 64. If end is (e.g.) 68, then 68 - 64 is 4 and we are only interested in the
first 4 bits of the word, so we must right shift by 60 bits, not 4 bits.