mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 08:38:09 -05:00
1 line
12 KiB
JavaScript
1 line
12 KiB
JavaScript
"use strict";(self.webpackChunkspeice_io=self.webpackChunkspeice_io||[]).push([["9437"],{64217:function(e,n,t){t.r(n),t.d(n,{assets:function(){return l},contentTitle:function(){return r},default:function(){return d},frontMatter:function(){return s},metadata:function(){return a},toc:function(){return c}});var a=t(16954),o=t(85893),i=t(50065);let s={slug:"2018/12/allocation-safety",title:"QADAPT - debug_assert! for allocations",date:new Date("2018-12-15T12:00:00.000Z"),authors:["bspeice"],tags:[]},r=void 0,l={authorsImageUrls:[void 0]},c=[{value:"Why an Allocator?",id:"why-an-allocator",level:2},{value:"Example 1",id:"example-1",level:3},{value:"Example 2",id:"example-2",level:3},{value:"Example 3",id:"example-3",level:3},{value:"Blowing Things Up",id:"blowing-things-up",level:2},{value:"Using a procedural macro",id:"using-a-procedural-macro",level:3},{value:"Using a regular macro",id:"using-a-regular-macro",level:3},{value:"Using function calls",id:"using-function-calls",level:3},{value:"Caveats",id:"caveats",level:3},{value:"Conclusion",id:"conclusion",level:2}];function h(e){let n={a:"a",blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,i.a)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.p,{children:"I think it's part of the human condition to ignore perfectly good advice when it comes our way. A\nbit over a month ago, I was dispensing sage wisdom for the ages:"}),"\n",(0,o.jsxs)(n.blockquote,{children:["\n",(0,o.jsxs)(n.p,{children:["I had a really great idea: build a custom allocator that allows you to track your own allocations.\nI gave it a shot, but learned very quickly: ",(0,o.jsx)(n.strong,{children:"never write your own allocator."})]}),"\n",(0,o.jsxs)(n.p,{children:["-- ",(0,o.jsx)(n.a,{href:"/2018/10/case-study-optimization",children:"me"})]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"I proceeded to ignore it, because we never really learn from our mistakes."}),"\n",(0,o.jsx)(n.p,{children:"There's another part of the human condition that derives joy from seeing things explode."}),"\n",(0,o.jsx)("center",{children:(0,o.jsx)(n.p,{children:(0,o.jsx)(n.img,{alt:"Explosions",src:t(27537).Z+"",width:"400",height:"280"})})}),"\n",(0,o.jsxs)(n.p,{children:["And ",(0,o.jsx)(n.em,{children:"that's"})," the part I'm going to focus on."]}),"\n",(0,o.jsx)(n.h2,{id:"why-an-allocator",children:"Why an Allocator?"}),"\n",(0,o.jsx)(n.p,{children:"So why, after complaining about allocators, would I still want to write one? There are three reasons\nfor that:"}),"\n",(0,o.jsxs)(n.ol,{children:["\n",(0,o.jsx)(n.li,{children:"Allocation/dropping is slow"}),"\n",(0,o.jsx)(n.li,{children:"It's difficult to know exactly when Rust will allocate or drop, especially when using code that\nyou did not write"}),"\n",(0,o.jsx)(n.li,{children:"I want automated tools to verify behavior, instead of inspecting by hand"}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["When I say \"slow,\" it's important to define the terms. If you're writing web applications, you'll\nspend orders of magnitude more time waiting for the database than you will the allocator. However,\nthere's still plenty of code where micro- or nano-seconds matter; think\n",(0,o.jsx)(n.a,{href:"https://www.youtube.com/watch?v=NH1Tta7purM",children:"finance"}),",\n",(0,o.jsx)(n.a,{href:"https://www.reddit.com/r/rust/comments/9hg7yj/synthesizer_progress_update/e6c291f",children:"real-time audio"}),",\n",(0,o.jsx)(n.a,{href:"https://polysync.io/blog/session-types-for-hearty-codecs/",children:"self-driving cars"}),", and\n",(0,o.jsx)(n.a,{href:"https://carllerche.github.io/bytes/bytes/index.html",children:"networking"}),". In these situations it's simply\nunacceptable for you to spend time doing things that are not your program, and waiting on the\nallocator is not cool."]}),"\n",(0,o.jsxs)(n.p,{children:["As I continue to learn Rust, it's difficult for me to predict where exactly allocations will happen.\nSo, I propose we play a quick trivia game: ",(0,o.jsx)(n.strong,{children:"Does this code invoke the allocator?"})]}),"\n",(0,o.jsx)(n.h3,{id:"example-1",children:"Example 1"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rust",children:"fn my_function() {\n let v: Vec<u8> = Vec::new();\n}\n"})}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:"No"}),": Rust ",(0,o.jsx)(n.a,{href:"https://doc.rust-lang.org/std/mem/fn.size_of.html",children:"knows how big"})," the ",(0,o.jsx)(n.code,{children:"Vec"})," type is,\nand reserves a fixed amount of memory on the stack for the ",(0,o.jsx)(n.code,{children:"v"})," vector. However, if we wanted to\nreserve extra space (using ",(0,o.jsx)(n.code,{children:"Vec::with_capacity"}),") the allocator would get invoked."]}),"\n",(0,o.jsx)(n.h3,{id:"example-2",children:"Example 2"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rust",children:"fn my_function() {\n let v: Box<Vec<u8>> = Box::new(Vec::new());\n}\n"})}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:"Yes"}),": Because Boxes allow us to work with things that are of unknown size, it has to allocate on\nthe heap. While the ",(0,o.jsx)(n.code,{children:"Box"})," is unnecessary in this snippet (release builds will optimize out the\nallocation), reserving heap space more generally is needed to pass a dynamically sized type to\nanother function."]}),"\n",(0,o.jsx)(n.h3,{id:"example-3",children:"Example 3"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rust",children:"fn my_function(v: Vec<u8>) {\n v.push(5);\n}\n"})}),"\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:"Maybe"}),": Depending on whether the Vector we were given has space available, we may or may not\nallocate. Especially when dealing with code that you did not author, it's difficult to verify that\nthings behave as you expect them to."]}),"\n",(0,o.jsx)(n.h2,{id:"blowing-things-up",children:"Blowing Things Up"}),"\n",(0,o.jsxs)(n.p,{children:["So, how exactly does QADAPT solve these problems? ",(0,o.jsx)(n.strong,{children:"Whenever an allocation or drop occurs in code\nmarked allocation-safe, QADAPT triggers a thread panic."})," We don't want to let the program continue\nas if nothing strange happened, ",(0,o.jsx)(n.em,{children:"we want things to explode"}),"."]}),"\n",(0,o.jsxs)(n.p,{children:["However, you don't want code to panic in production because of circumstances you didn't predict.\nJust like ",(0,o.jsx)(n.a,{href:"https://doc.rust-lang.org/std/macro.debug_assert.html",children:(0,o.jsx)(n.code,{children:"debug_assert!"})}),", ",(0,o.jsx)(n.strong,{children:"QADAPT will\nstrip out its own code when building in release mode to guarantee no panics and no performance\nimpact."})]}),"\n",(0,o.jsx)(n.p,{children:"Finally, there are three ways to have QADAPT check that your code will not invoke the allocator:"}),"\n",(0,o.jsx)(n.h3,{id:"using-a-procedural-macro",children:"Using a procedural macro"}),"\n",(0,o.jsx)(n.p,{children:"The easiest method, watch an entire function for allocator invocation:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rust",children:"use qadapt::no_alloc;\nuse qadapt::QADAPT;\n\n#[global_allocator]\nstatic Q: QADAPT = QADAPT;\n\n#[no_alloc]\nfn push_vec(v: &mut Vec<u8>) {\n // This triggers a panic if v.len() == v.capacity()\n v.push(5);\n}\n\nfn main() {\n let v = Vec::with_capacity(1);\n\n // This will *not* trigger a panic\n push_vec(&v);\n\n // This *will* trigger a panic\n push_vec(&v);\n}\n"})}),"\n",(0,o.jsx)(n.h3,{id:"using-a-regular-macro",children:"Using a regular macro"}),"\n",(0,o.jsx)(n.p,{children:"For times when you need more precision:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rust",children:"use qadapt::assert_no_alloc;\nuse qadapt::QADAPT;\n\n#[global_allocator]\nstatic Q: QADAPT = QADAPT;\n\nfn main() {\n let v = Vec::with_capacity(1);\n\n // No allocations here, we already have space reserved\n assert_no_alloc!(v.push(5));\n\n // Even though we remove an item, it doesn't trigger a drop\n // because it's a scalar. If it were a `Box<_>` type,\n // a drop would trigger.\n assert_no_alloc!({\n v.pop().unwrap();\n });\n}\n"})}),"\n",(0,o.jsx)(n.h3,{id:"using-function-calls",children:"Using function calls"}),"\n",(0,o.jsx)(n.p,{children:"Both the most precise and most tedious:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rust",children:"use qadapt::enter_protected;\nuse qadapt::exit_protected;\nuse qadapt::QADAPT;\n\n#[global_allocator]\nstatic Q: QADAPT = QADAPT;\n\nfn main() {\n // This triggers an allocation (on non-release builds)\n let v = Vec::with_capacity(1);\n\n enter_protected();\n // This does not trigger an allocation because we've reserved size\n v.push(0);\n exit_protected();\n\n // This triggers an allocation because we ran out of size,\n // but doesn't panic because we're no longer protected.\n v.push(1);\n}\n"})}),"\n",(0,o.jsx)(n.h3,{id:"caveats",children:"Caveats"}),"\n",(0,o.jsx)(n.p,{children:"It's important to point out that QADAPT code is synchronous, so please be careful when mixing in\nasynchronous functions:"}),"\n",(0,o.jsx)(n.pre,{children:(0,o.jsx)(n.code,{className:"language-rust",children:"use futures::future::Future;\nuse futures::future::ok;\n\n#[no_alloc]\nfn async_capacity() -> impl Future<Item=Vec<u8>, Error=()> {\n ok(12).and_then(|e| Ok(Vec::with_capacity(e)))\n}\n\nfn main() {\n // This doesn't trigger a panic because the `and_then` closure\n // wasn't run during the function call.\n async_capacity();\n\n // Still no panic\n assert_no_alloc!(async_capacity());\n\n // This will panic because the allocation happens during `unwrap`\n // in the `assert_no_alloc!` macro\n assert_no_alloc!(async_capacity().poll().unwrap());\n}\n"})}),"\n",(0,o.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,o.jsxs)(n.p,{children:["While there's a lot more to writing high-performance code than managing your usage of the allocator,\nit's critical that you do use the allocator correctly. QADAPT will verify that your code is doing\nwhat you expect. It's usable even on stable Rust from version 1.31 onward, which isn't the case for\nmost allocators. Version 1.0 was released today, and you can check it out over at\n",(0,o.jsx)(n.a,{href:"https://crates.io/crates/qadapt",children:"crates.io"})," or on ",(0,o.jsx)(n.a,{href:"https://github.com/bspeice/qadapt",children:"github"}),"."]}),"\n",(0,o.jsx)(n.p,{children:"I'm hoping to write more about high-performance Rust in the future, and I expect that QADAPT will\nhelp guide that. If there are topics you're interested in, let me know in the comments below!"})]})}function d(e={}){let{wrapper:n}={...(0,i.a)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(h,{...e})}):h(e)}},27537:function(e,n,t){t.d(n,{Z:function(){return a}});let a=t.p+"assets/images/watch-the-world-burn-630e740c91d090f5790a3f4e103f1142.webp"},50065:function(e,n,t){t.d(n,{Z:function(){return r},a:function(){return s}});var a=t(67294);let o={},i=a.createContext(o);function s(e){let n=a.useContext(i);return a.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),a.createElement(i.Provider,{value:n},e.children)}},16954:function(e){e.exports=JSON.parse('{"permalink":"/2018/12/allocation-safety","source":"@site/blog/2018-12-15-allocation-safety/index.mdx","title":"QADAPT - debug_assert! for allocations","description":"I think it\'s part of the human condition to ignore perfectly good advice when it comes our way. A","date":"2018-12-15T12:00:00.000Z","tags":[],"readingTime":4.775,"hasTruncateMarker":true,"authors":[{"name":"Bradlee Speice","socials":{"github":"https://github.com/bspeice"},"key":"bspeice","page":null}],"frontMatter":{"slug":"2018/12/allocation-safety","title":"QADAPT - debug_assert! for allocations","date":"2018-12-15T12:00:00.000Z","authors":["bspeice"],"tags":[]},"unlisted":false,"lastUpdatedAt":1731204300000,"prevItem":{"title":"Allocations in Rust: Foreword","permalink":"/2019/02/understanding-allocations-in-rust"},"nextItem":{"title":"More \\"what companies really mean\\"","permalink":"/2018/12/what-small-business-really-means"}}')}}]); |