aligncheck helps to find inefficiently packed structs

polaris · · 823 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p><code>go get github.com/opennota/check/cmd/aligncheck</code></p> <p>Invoking <code>aligncheck</code> requires no super powers:</p> <pre><code>$ aligncheck net/http net/http: /usr/lib/go/src/net/http/server.go:123:6: struct conn could have size 160 (currently 168) net/http: /usr/lib/go/src/net/http/server.go:315:6: struct response could have size 152 (currently 176) net/http: /usr/lib/go/src/net/http/transfer.go:37:6: struct transferWriter could have size 96 (currently 112) net/http: /usr/lib/go/src/net/http/transport.go:49:6: struct Transport could have size 136 (currently 144) net/http: /usr/lib/go/src/net/http/transport.go:811:6: struct persistConn could have size 160 (currently 176) </code></pre> <p>What it shows are the current sizes of the structs and the minimal sizes that can be achieved if you rearrange the fields.</p> <p>An example. Let&#39;s take the <code>conn</code> struct from <code>net/http</code>:</p> <pre><code>type conn struct { remoteAddr string // network address of remote side server *Server // the Server on which the connection arrived rwc net.Conn // i/o connection w io.Writer // checkConnErrorWriter&#39;s copy of wrc, not zeroed on Hijack werr error // any errors writing to w sr liveSwitchReader // where the LimitReader reads from; usually the rwc lr *io.LimitedReader // io.LimitReader(sr) buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio-&gt;limitReader-&gt;sr-&gt;rwc tlsState *tls.ConnectionState // or nil when not using TLS lastMethod string // method of previous request, or &#34;&#34; mu sync.Mutex // guards the following clientGone bool // if client has disconnected mid-request closeNotifyc chan bool // made lazily hijackedv bool // connection has been hijacked by handler } </code></pre> <p>and move the <code>clientGone</code> 1-byte bool field down a step:</p> <pre><code>type conn struct { remoteAddr string // network address of remote side server *Server // the Server on which the connection arrived rwc net.Conn // i/o connection w io.Writer // checkConnErrorWriter&#39;s copy of wrc, not zeroed on Hijack werr error // any errors writing to w sr liveSwitchReader // where the LimitReader reads from; usually the rwc lr *io.LimitedReader // io.LimitReader(sr) buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio-&gt;limitReader-&gt;sr-&gt;rwc tlsState *tls.ConnectionState // or nil when not using TLS lastMethod string // method of previous request, or &#34;&#34; mu sync.Mutex // guards the following closeNotifyc chan bool // made lazily clientGone bool // if client has disconnected mid-request hijackedv bool // connection has been hijacked by handler } </code></pre> <p>Now run <code>aligncheck</code> again. It will show you that you&#39;re down from 168 to 160 bytes.</p> <p><a href="https://github.com/opennota/check">The repository.</a></p> <hr/>**评论:**<br/><br/>infogulch: <pre><p>AFAICT the Go <a href="https://golang.org/ref/spec#Struct_types">spec on structs</a> doesn&#39;t guarantee that the physical layout of structs matches the semantic layout in their definition. Therefore, it should be possible for the compiler to rearrange fields during compilation in a way that is transparent to the user.</p> <p>For example, http.Transport in the playground <a href="https://play.golang.org/p/-yr6RD7sUb">is 104 bytes</a>, much smaller than even what aligncheck suggests is possible. Are we sure the compiler doesn&#39;t do this optimization already?</p></pre>codehusker: <pre><p>Running your snippet locally produces 144 bytes. I think 104 bytes is a playground anomaly.</p></pre>dtfinch: <pre><p>The playground compiles to run in Google&#39;s <a href="https://github.com/golang/go/wiki/NativeClient">Native Client</a> sandbox. Ints and pointers are smaller on 32-bit.</p></pre>sbinet: <pre><p>I don&#39;t know whether gc applies this kind of aggressive optimization but considering that this may break programs using reflect.StructField.Offset, or reflect.Value.Field(i int), it would need to also maintain a set of old/new offsets conversion tables for each type... sounds wasteful.</p></pre>szabba: <pre><p>How would you expect reflection to work without such information being stored somewhere in the binary?</p></pre>infogulch: <pre><p>reflect.Value.Field &#34;returns the i&#39;th field of the struct v&#34;, which should always be the semantic field number. I imagine you can still use <a href="https://golang.org/pkg/reflect/#StructField" rel="nofollow">reflect.StructField</a> with Offset reliably as long as you don&#39;t do something stupid like cache the Offset externally and load it in a recompiled process. (edit: also don&#39;t make silly assumptions like assuming <code>Field(i).Offset &lt; Field(i+1).Offset</code>)</p> <p>In addition, I&#39;m pretty sure the behavior of reflect and unsafe packages are not fully covered in the <a href="https://golang.org/doc/go1compat" rel="nofollow">Go 1 Compatibility</a> guarantee.</p></pre>pdq: <pre><p>Does this take into consideration mutexes?</p> <p>For example, the common Go idiom is that struct member variables below (after) the mutex are guarded behind the mutex. So you would not want to move a variable &#34;across&#34; a mutex.</p> <p>For example:</p> <pre><code>aaa bool mu sync.Mutex // guards the following clientGone bool // if client has disconnected mid-request bbb bool closeNotifyc chan bool // made lazily hijackedv bool // connection has been hijacked by handler </code></pre> <p>In this case you would not want to move aaa next to bbb, even though it may help with packing, because aaa is not a locked/shared member, but bbb is.</p></pre>opennota: <pre><p>No, <code>aligncheck</code> is pretty stupid. I wrote it in an evening on a whim.</p></pre>dchapes: <pre><p>IMO, readability or other reasons for a specific field order should always take precedence over (pre-mature?) optimization of struct space. Only if you have an issue and profiling shows you where (e.g. you allocate a lot of one type of struct) should you worry about trying to save a byte or three by re-ordering.</p></pre>hzck: <pre><p>Does this tool find out about how to rearrange the struct variables or do you have to figure that out yourself?</p></pre>opennota: <pre><p>Not yet.</p></pre>upboatact: <pre><p>I&#39;m using your tools mostly through gometalinter, thanks alot for them, they come in really handy!</p></pre>captncraig: <pre><p>Wasn&#39;t there a web visualizer of struct packing somewhere? I liked that, but lost it. EDIT: found it: <a href="http://golang-sizeof.tips/">http://golang-sizeof.tips/</a></p></pre>Ainar-G: <pre><p>Thanks, looks great!</p> <p>Obligatory: <a href="http://www.catb.org/esr/structure-packing/" rel="nofollow">http://www.catb.org/esr/structure-packing/</a>. It&#39;s about C structs, but AFAIK most of it also applies to Go structs.</p></pre>opennota: <pre><blockquote> <p>Obligatory: <a href="http://www.catb.org/esr/structure-packing/" rel="nofollow">http://www.catb.org/esr/structure-packing/</a></p> </blockquote> <p>Yes.</p></pre>Fwippy: <pre><p>Very cool! Is this architecture-dependent? That is, for best results, should I run it on each architecture that I plan to deploy on?</p></pre>opennota: <pre><blockquote> <p>Is this architecture-dependent?</p> </blockquote> <p>Yes. You can expect different results on 32-bit and 64-bit systems.</p></pre>matttproud: <pre><p>I was just thinking about building a tool to do exactly this. Glad someone beat me to it.</p></pre>michaelKlumpy: <pre><p>can someone eli5 how the arrangement can mess up sizes?<br/> Does this happen when there are many fractions of an adress?<br/> 32bit 32bit 64bit will use 128bit,<br/> while 32 bit 64 bit 32bit will use 192bit?</p></pre>szabba: <pre><p>Presumably in a similiar way as they do in C <a href="http://www.catb.org/esr/structure-packing/" rel="nofollow">http://www.catb.org/esr/structure-packing/</a> Not sure if C compilers are allowed to rearrange in-memory locations of the struct fields.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

823 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传