Jekyll2020-10-22T16:45:29+00:00https://meltware.com/feed.xmlMeltwareA blog, in which we demonstrate all software is held together by duct tape and bubble gum.
Nikhil BeneschDebugging async generator errors in Rust2020-10-21T00:00:00+00:002020-10-21T00:00:00+00:00https://meltware.com/2020/10/21/rust-async-nonsense<p>My colleague <a href="http://mattjibson.com">Matt Jibson</a> and I recently found ourselves
in the unfortunate situation of debugging this hefty async/await-related error
from the Rust compiler:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error[E0277]: `(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)` cannot be shared between threads safely
--> src/materialized/src/mux.rs:138:100
|
138 | async fn handle_connection(&self, conn: SniffedStream<TcpStream>) -> Result<(), anyhow::Error> {
| ____________________________________________________________________________________________________^
139 | | self.handle_connection(conn).await
140 | | }
| |_____^ `(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)`
= note: required because of the requirements on the impl of `std::marker::Sync` for `std::ptr::Unique<(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)>`
= note: required because it appears within the type `std::boxed::Box<(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)>`
= note: required because it appears within the type `std::option::Option<std::boxed::Box<(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)>>`
= note: required because it appears within the type `coord::session::Portal`
= note: required because of the requirements on the impl of `std::marker::Send` for `&coord::session::Portal`
= note: required because it appears within the type `for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8> {std::future::ResumeTy, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, std::string::String, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, &'s mut coord::SessionClient, coord::SessionClient, &'t0 coord::session::Session, &'t1 mut coord::session::Session, &'t2 str, &'t3 std::string::String, std::option::Option<&'t4 coord::session::Portal>, tokio_postgres::error::sqlstate::SqlState, impl futures::Future, (), &'t7 coord::session::Portal, impl futures::Future}`
= note: required because it appears within the type `[static generator@pgwire::protocol::StateMachine::<A>::describe_portal::#0 0:&mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, 1:std::string::String for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8> {std::future::ResumeTy, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, std::string::String, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, &'s mut coord::SessionClient, coord::SessionClient, &'t0 coord::session::Session, &'t1 mut coord::session::Session, &'t2 str, &'t3 std::string::String, std::option::Option<&'t4 coord::session::Portal>, tokio_postgres::error::sqlstate::SqlState, impl futures::Future, (), &'t7 coord::session::Portal, impl futures::Future}]`
= note: required because it appears within the type `std::future::from_generator::GenFuture<[static generator@pgwire::protocol::StateMachine::<A>::describe_portal::#0 0:&mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, 1:std::string::String for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8> {std::future::ResumeTy, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, std::string::String, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, &'s mut coord::SessionClient, coord::SessionClient, &'t0 coord::session::Session, &'t1 mut coord::session::Session, &'t2 str, &'t3 std::string::String, std::option::Option<&'t4 coord::session::Portal>, tokio_postgres::error::sqlstate::SqlState, impl futures::Future, (), &'t7 coord::session::Portal, impl futures::Future}]>`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10, 't11> {std::future::ResumeTy, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, &'s mut pgwire::codec::FramedConn<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, pgwire::codec::FramedConn<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, (), std::option::Option<pgwire::message::FrontendMessage>, std::time::Instant, &'t1 str, std::string::String, impl futures::Future, std::vec::Vec<u32>, impl futures::Future, std::vec::Vec<pgrepr::format::Format>, std::vec::Vec<std::option::Option<std::vec::Vec<u8>>>, impl futures::Future, i32, usize, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future}`
= note: required because it appears within the type `[static generator@pgwire::protocol::StateMachine::<A>::advance_ready::#0 0:&mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10, 't11> {std::future::ResumeTy, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, &'s mut pgwire::codec::FramedConn<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, pgwire::codec::FramedConn<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, (), std::option::Option<pgwire::message::FrontendMessage>, std::time::Instant, &'t1 str, std::string::String, impl futures::Future, std::vec::Vec<u32>, impl futures::Future, std::vec::Vec<pgrepr::format::Format>, std::vec::Vec<std::option::Option<std::vec::Vec<u8>>>, impl futures::Future, i32, usize, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future}]`
= note: required because it appears within the type `std::future::from_generator::GenFuture<[static generator@pgwire::protocol::StateMachine::<A>::advance_ready::#0 0:&mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10, 't11> {std::future::ResumeTy, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, &'s mut pgwire::codec::FramedConn<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, pgwire::codec::FramedConn<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, (), std::option::Option<pgwire::message::FrontendMessage>, std::time::Instant, &'t1 str, std::string::String, impl futures::Future, std::vec::Vec<u32>, impl futures::Future, std::vec::Vec<pgrepr::format::Format>, std::vec::Vec<std::option::Option<std::vec::Vec<u8>>>, impl futures::Future, i32, usize, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future, impl futures::Future}]>`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `for<'r, 's, 't0, 't1> {std::future::ResumeTy, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, i32, std::vec::Vec<(std::string::String, std::string::String)>, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, (), pgwire::protocol::State, impl futures::Future, impl futures::Future, coord::SessionClient, impl futures::Future}`
= note: required because it appears within the type `[static generator@pgwire::protocol::StateMachine::<A>::run::#0 0:pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, 1:i32, 2:std::vec::Vec<(std::string::String, std::string::String)> for<'r, 's, 't0, 't1> {std::future::ResumeTy, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, i32, std::vec::Vec<(std::string::String, std::string::String)>, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, (), pgwire::protocol::State, impl futures::Future, impl futures::Future, coord::SessionClient, impl futures::Future}]`
= note: required because it appears within the type `std::future::from_generator::GenFuture<[static generator@pgwire::protocol::StateMachine::<A>::run::#0 0:pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, 1:i32, 2:std::vec::Vec<(std::string::String, std::string::String)> for<'r, 's, 't0, 't1> {std::future::ResumeTy, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, i32, std::vec::Vec<(std::string::String, std::string::String)>, &'r mut pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, (), pgwire::protocol::State, impl futures::Future, impl futures::Future, coord::SessionClient, impl futures::Future}]>`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10> {std::future::ResumeTy, &'r pgwire::Server, ore::netio::SniffedStream<tokio::net::TcpStream>, pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>, &'s mut pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>, impl futures::Future, (), std::result::Result<pgwire::message::FrontendStartupMessage, std::io::Error>, pgwire::message::FrontendStartupMessage, i32, std::vec::Vec<(std::string::String, std::string::String)>, u32, coord::SessionClient, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, bool, pgwire::Server, &'t1 coord::Client, coord::Client, &'t2 mut coord::Client, impl futures::Future, &'t4 mut ore::netio::SniffedStream<tokio::net::TcpStream>, u8, [u8; 1], &'t5 [u8], &'t6 [u8; 1], tokio::io::util::write_all::WriteAll<'t7, ore::netio::SniffedStream<tokio::net::TcpStream>>, &'t8 openssl::ssl::SslAcceptor, impl futures::Future, tokio::io::util::write_all::WriteAll<'t10, pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>}`
= note: required because it appears within the type `[static generator@pgwire::Server::handle_connection::#0 0:&pgwire::Server, 1:ore::netio::SniffedStream<tokio::net::TcpStream> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10> {std::future::ResumeTy, &'r pgwire::Server, ore::netio::SniffedStream<tokio::net::TcpStream>, pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>, &'s mut pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>, impl futures::Future, (), std::result::Result<pgwire::message::FrontendStartupMessage, std::io::Error>, pgwire::message::FrontendStartupMessage, i32, std::vec::Vec<(std::string::String, std::string::String)>, u32, coord::SessionClient, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, bool, pgwire::Server, &'t1 coord::Client, coord::Client, &'t2 mut coord::Client, impl futures::Future, &'t4 mut ore::netio::SniffedStream<tokio::net::TcpStream>, u8, [u8; 1], &'t5 [u8], &'t6 [u8; 1], tokio::io::util::write_all::WriteAll<'t7, ore::netio::SniffedStream<tokio::net::TcpStream>>, &'t8 openssl::ssl::SslAcceptor, impl futures::Future, tokio::io::util::write_all::WriteAll<'t10, pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>}]`
= note: required because it appears within the type `std::future::from_generator::GenFuture<[static generator@pgwire::Server::handle_connection::#0 0:&pgwire::Server, 1:ore::netio::SniffedStream<tokio::net::TcpStream> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8, 't9, 't10> {std::future::ResumeTy, &'r pgwire::Server, ore::netio::SniffedStream<tokio::net::TcpStream>, pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>, &'s mut pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>, impl futures::Future, (), std::result::Result<pgwire::message::FrontendStartupMessage, std::io::Error>, pgwire::message::FrontendStartupMessage, i32, std::vec::Vec<(std::string::String, std::string::String)>, u32, coord::SessionClient, pgwire::protocol::StateMachine<pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>, impl futures::Future, bool, pgwire::Server, &'t1 coord::Client, coord::Client, &'t2 mut coord::Client, impl futures::Future, &'t4 mut ore::netio::SniffedStream<tokio::net::TcpStream>, u8, [u8; 1], &'t5 [u8], &'t6 [u8; 1], tokio::io::util::write_all::WriteAll<'t7, ore::netio::SniffedStream<tokio::net::TcpStream>>, &'t8 openssl::ssl::SslAcceptor, impl futures::Future, tokio::io::util::write_all::WriteAll<'t10, pgwire::server::Conn<ore::netio::SniffedStream<tokio::net::TcpStream>>>}]>`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `for<'r, 's> {std::future::ResumeTy, &'r pgwire::Server, ore::netio::SniffedStream<tokio::net::TcpStream>, impl futures::Future, ()}`
= note: required because it appears within the type `[static generator@src/materialized/src/mux.rs:138:100: 140:6 _self:&pgwire::Server, conn:ore::netio::SniffedStream<tokio::net::TcpStream> for<'r, 's> {std::future::ResumeTy, &'r pgwire::Server, ore::netio::SniffedStream<tokio::net::TcpStream>, impl futures::Future, ()}]`
= note: required because it appears within the type `std::future::from_generator::GenFuture<[static generator@src/materialized/src/mux.rs:138:100: 140:6 _self:&pgwire::Server, conn:ore::netio::SniffedStream<tokio::net::TcpStream> for<'r, 's> {std::future::ResumeTy, &'r pgwire::Server, ore::netio::SniffedStream<tokio::net::TcpStream>, impl futures::Future, ()}]>`
= note: required because it appears within the type `impl futures::Future`
= note: required because it appears within the type `impl futures::Future`
= note: required for the cast to the object type `dyn futures::Future<Output = std::result::Result<(), anyhow::Error>> + std::marker::Send`
</code></pre></div></div>
<p>Good luck with that, eh?</p>
<p>It turns out there is a clever way to convince the compiler to spit out a far
more helpful error messages. I’ve now forgotten this trick twice and so have had
to invent it thrice, so I figured it was time to write it down. Maybe you’ll
find it useful too.</p>
<p>If you’re not interested in the background, just <a href="#successful-strategy-demand-send-earlier">skip down to the
trick</a>.</p>
<h2 id="background">Background</h2>
<p>The code in question is Materialize’s implementation of the PostgreSQL
network protocol<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup>. Roughly speaking, the protocol works like this:</p>
<ol>
<li>A client sends a SQL query of interest to the Materialize server.</li>
<li>The server plans the SQL query. If the query is valid, the server
establishes a “portal” for the query.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">2</a></sup></li>
<li>The client optionally asks questions about the portal, like “how many
columns will be in the result set?” and “what are the types of those
columns?”</li>
<li>The client asks the server to execute the portal, optionally asking that
no more than <em>n</em> rows are returned.</li>
<li>The server executes the query, and sends up to <em>n</em> rows back to the client.
If the result contains more than <em>n</em> rows, the server additionally tells
the client “you might want to ask for more rows.”</li>
<li>The client optionally asks for another batch of up to <em>n</em> rows.</li>
<li>Steps 6 and 7 repeat until the result set is exhausted.</li>
</ol>
<p>You can view the gory details of the full protocol <a href="https://github.com/MaterializeInc/materialize/blob/8f0fcc4d94869a6555156eaf51b6bc4e0ef8de4b/src/pgwire/src/protocol.rs">on GitHub</a>.
I’m going to do my best to present a simplified version here. Like most modern
networking code in Rust, our implementation is entirely asynchronous,
using the recently-stabilized <a href="https://rust-lang.github.io/async-book/01_getting_started/04_async_await_primer.html">async/await</a> syntax.</p>
<p>Today, the code that handles portal execution looks roughly like this:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">Session</span> <span class="p">{</span>
<span class="n">portals</span><span class="p">:</span> <span class="n">HashMap</span><span class="o"><</span><span class="nb">String</span><span class="p">,</span> <span class="n">Portal</span><span class="o">></span><span class="p">,</span>
<span class="p">}</span>
<span class="k">struct</span> <span class="n">Portal</span> <span class="p">{</span>
<span class="n">sql</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span>
<span class="n">remaining_rows</span><span class="p">:</span> <span class="nb">Option</span><span class="o"><</span><span class="nb">Vec</span><span class="o"><</span><span class="n">Row</span><span class="o">>></span><span class="p">,</span>
<span class="p">}</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">execute_query</span><span class="p">(</span><span class="n">sql</span><span class="p">:</span> <span class="o">&</span><span class="nb">str</span><span class="p">)</span> <span class="k">-></span> <span class="n">Result</span><span class="o"><</span><span class="nb">Vec</span><span class="o"><</span><span class="n">Row</span><span class="o">></span><span class="p">,</span> <span class="n">Error</span><span class="o">></span> <span class="p">{</span> <span class="cm">/* ... */</span> <span class="p">}</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">handle_execute</span><span class="p">(</span>
<span class="n">conn</span><span class="p">:</span> <span class="n">Conn</span><span class="p">,</span>
<span class="n">session</span><span class="p">:</span> <span class="n">Session</span><span class="p">,</span>
<span class="n">portal_name</span><span class="p">:</span> <span class="o">&</span><span class="nb">str</span><span class="p">,</span>
<span class="n">max_rows</span><span class="p">:</span> <span class="nb">usize</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-></span> <span class="n">Result</span><span class="o"><</span><span class="p">(),</span> <span class="n">Error</span><span class="o">></span> <span class="p">{</span>
<span class="k">let</span> <span class="n">portal</span> <span class="o">=</span> <span class="k">match</span> <span class="n">session</span><span class="py">.portals</span><span class="nf">.get_mut</span><span class="p">(</span><span class="n">portal_name</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">Some</span><span class="p">(</span><span class="n">portal</span><span class="p">)</span> <span class="k">=></span> <span class="n">portal</span><span class="p">,</span>
<span class="nb">None</span> <span class="k">=></span> <span class="nd">bail!</span><span class="p">(</span><span class="s">"unknown portal {}"</span><span class="p">,</span> <span class="n">portal_name</span><span class="p">),</span>
<span class="p">};</span>
<span class="k">if</span> <span class="n">portal</span><span class="py">.remaining_rows</span><span class="nf">.is_none</span><span class="p">()</span> <span class="p">{</span>
<span class="n">portal</span><span class="py">.remaining_rows</span> <span class="o">=</span> <span class="nf">Some</span><span class="p">(</span><span class="nf">execute_query</span><span class="p">(</span><span class="o">&</span><span class="n">portal</span><span class="py">.sql</span><span class="p">)</span><span class="py">.await</span><span class="o">?</span><span class="p">);</span>
<span class="p">};</span>
<span class="k">let</span> <span class="n">rows</span> <span class="o">=</span> <span class="n">portal</span><span class="py">.remaining_rows</span><span class="nf">.as_mut</span><span class="p">()</span><span class="nf">.unwrap</span><span class="p">();</span>
<span class="k">for</span> <span class="n">row</span> <span class="n">in</span> <span class="n">rows</span><span class="nf">.drain</span><span class="p">(</span><span class="o">..</span><span class="n">max_rows</span><span class="p">)</span> <span class="p">{</span>
<span class="n">conn</span><span class="nf">.send</span><span class="p">(</span><span class="n">row</span><span class="p">)</span><span class="py">.await</span><span class="o">?</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">rows</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="p">{</span>
<span class="n">conn</span><span class="nf">.send</span><span class="p">(</span><span class="nn">Message</span><span class="p">::</span><span class="n">CommandComplete</span><span class="p">)</span><span class="py">.await</span><span class="o">?</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">conn</span><span class="nf">.send</span><span class="p">(</span><span class="nn">Message</span><span class="p">::</span><span class="n">PortalSuspended</span><span class="p">)</span><span class="py">.await</span><span class="o">?</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="supporting-streaming-sql">Supporting streaming SQL</h2>
<p>This code above works well enough, but it has the limitation that the
<code class="language-plaintext highlighter-rouge">execute_query</code> method must produce all of the rows up front as a <code class="language-plaintext highlighter-rouge">Vec<Row></code>.
Materialize is, after all, a <em>streaming</em> database: it incrementally computes
changes to queries as the input data changes. Wouldn’t it be nice if we could
stream those changes to the client as they happened?</p>
<p>The goal is to change <code class="language-plaintext highlighter-rouge">execute_query</code> to return a
<a href="https://docs.rs/futures/0.3.6/futures/stream/trait.Stream.html"><code class="language-plaintext highlighter-rouge">Stream<Item = Row></code></a> so that new rows could be shipped off to the
client as soon as they were ready. The naive diff would look something like
this:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@@ -4,10 +4,10 @@</span>
struct Portal {
sql: String,
<span class="gd">- remaining_rows: Option<Vec<Row>>,
</span><span class="gi">+ remaining_rows: Option<Box<dyn Stream<Item = Row> + Send + Unpin>>,
</span> }
-async fn execute_query(sql: &str) -> Result<Vec<Row>, Error> { /* ... */ }
<span class="gi">+fn execute_query(sql: &str) -> Result<Box<dyn Stream<Item = Row> + Send + Unpin>, Error> { /* ... */ }
</span>
async fn handle_execute(
conn: Conn,
<span class="p">@@ -25,11 +25,11 @@</span>
};
let rows = portal.remaining_rows.as_mut().unwrap();
- for row in rows.drain(..max_rows) {
<span class="gi">+ while let Some(row) = rows.take(max_rows).next().await {
</span> conn.send(row).await?;
}
- if rows.is_empty() {
<span class="gi">+ if rows.peek().is_none() {
</span> conn.send(Message::CommandComplete).await?;
} else {
conn.send(Message::PortalSuspended).await?;
</code></pre></div></div>
<p>But that would be too easy. This straightforward diff triggers the six-page
“future cannot be shared between threads safely” error mentioned at the
beginning of the post.</p>
<p>To explain the error message to the extent possible: our PostgreSQL server is
compiled into a giant state machine by the Rust compiler. There is an entry
point to the state machine that looks like this:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">fn</span> <span class="nf">handle_connection</span><span class="p">(</span><span class="n">conn</span><span class="p">:</span> <span class="n">Connection</span><span class="p">)</span>
</code></pre></div></div>
<p>This is desugared by the Rust compiler into:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">handle_connection</span><span class="p">(</span><span class="n">conn</span><span class="p">:</span> <span class="n">Conn</span><span class="p">)</span> <span class="k">-></span> <span class="k">impl</span> <span class="n">Future</span><span class="o"><</span><span class="n">Output</span> <span class="o">=</span> <span class="p">()</span><span class="o">></span>
</code></pre></div></div>
<p>The returned <a href="https://doc.rust-lang.org/std/future/trait.Future.html"><code class="language-plaintext highlighter-rouge">Future</code></a> has a <code class="language-plaintext highlighter-rouge">poll</code> method that drives the state
machine from await point to await point. For some unknown reason the
generated future does not implement the <a href="https://doc.rust-lang.org/stable/std/marker/trait.Sync.html"><code class="language-plaintext highlighter-rouge">Sync</code></a> trait, but the compiler
insists that it needs to.</p>
<p>So what to do?</p>
<h2 id="failed-approaches">Failed approaches</h2>
<p>My usual first approach to dealing with less-than-helpful error messages like
this is (not sarcastically) to think really hard. Oftentimes you already know,
subconciously, why the code you have written is unsafe—e.g., you’re trying to
share an <a href="https://doc.rust-lang.org/std/rc/struct.Rc.html"><code class="language-plaintext highlighter-rouge">Rc</code></a> across threads—and the compiler’s reminder is enough to jog
your memory.</p>
<p>I remember a study, though I can’t find the source now, in which programmers
were tasked with finding bugs in a code sample that contained several bugs. If
the programmer was told nothing more about the code, they would often find only
some of the bugs, or none at all. But if the programmer was told that the
program contained <em>n</em> bugs, they were much more likely to find <em>n</em> bugs, if not
more! Bad compiler errors remind me of that study. It’s weirdly much easier to
find your bug when your compiler has told you that it exists, even if it can’t
tell you where it is or how to fix it.</p>
<p>But the “think hard” strategy proved fruitless here. The code in question is
very much safe according to my mental model. The connection future takes a
<code class="language-plaintext highlighter-rouge">Stream</code> that it owns, stores it in a <code class="language-plaintext highlighter-rouge">Portal</code> that it owns, and reads messages
out of the stream in response to execute requests. There are no other futures
that have access to the <code class="language-plaintext highlighter-rouge">Session</code>, <code class="language-plaintext highlighter-rouge">Portal</code>, or <code class="language-plaintext highlighter-rouge">Stream</code> objects.</p>
<p>My next approach is to apply the most obvious, mechanical solution possible.
Matt does this too, I learned. In this case the compiler is complaining about
a future not implementing <code class="language-plaintext highlighter-rouge">Sync</code>. Maybe we just need to declare that our
stream is <code class="language-plaintext highlighter-rouge">Sync</code> like so and everything will work out:</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">@@ -4,10 +4,10 @@</span>
struct Portal {
sql: String,
<span class="gd">- remaining_rows: Option<Box<dyn Stream<Item = Row> + Send + Unpin>>,
</span><span class="gi">+ remaining_rows: Option<Box<dyn Stream<Item = Row> + Send + Sync + Unpin>>,
</span> }
-fn execute_query(sql: &str) -> Result<Box<dyn Stream<Item = Row> + Send + Unpin>, Error> { /* ... */ }
<span class="gi">+fn execute_query(sql: &str) -> Result<Box<dyn Stream<Item = Row> + Send + Sync + Unpin>, Error> { /* ... */ }
</span>
async fn handle_execute(
conn: Conn,
</code></pre></div></div>
<p>As you might have guessed, this doesn’t work. It turns out the stream that our
<code class="language-plaintext highlighter-rouge">execute_query</code> function returns is not <code class="language-plaintext highlighter-rouge">Sync</code> and cannot be made <code class="language-plaintext highlighter-rouge">Sync</code> for
reasons fundamental to the design of our communication plane.</p>
<p>But, still, why does the overall protocol future need to be <code class="language-plaintext highlighter-rouge">Sync</code>? This future
is not shared between threads.</p>
<h2 id="successful-strategy-demand-send-earlier">Successful strategy: demand <code class="language-plaintext highlighter-rouge">Send</code> earlier</h2>
<p>If you Google enough terms related to “future”, “rust”, “async/await”, “bad
error messages”, and “shared between threads”, eventually you will surface this
blog post from the Rust team entitled <a href="https://blog.rust-lang.org/inside-rust/2019/10/11/AsyncAwait-Not-Send-Error-Improvements.html">“Improving async-await’s ‘Future is not
Send’ diagnostic”</a>. This blog post claims that many error messages of
this class have been vastly improved, like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error[E0277]: `std::sync::MutexGuard<'_, u32>` cannot be sent between threads safely
--> src/main.rs:23:5
|
5 | fn is_send<T: Send>(t: T) {
| ------- ---- required by this bound in `is_send`
...
23 | is_send(foo());
| ^^^^^^^ `std::sync::MutexGuard<'_, u32>` cannot be sent between threads safely
|
= help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, u32>`
note: future does not implement `std::marker::Send` as this value is used across an await
--> src/main.rs:15:3
|
14 | let g = x.lock().unwrap();
| - has type `std::sync::MutexGuard<'_, u32>`
15 | baz().await;
| ^^^^^^^^^^^ await occurs here, with `g` maybe used later
16 | }
| - `g` is later dropped here
</code></pre></div></div>
<p>That is a far cry from the error message at the start of this blog post.</p>
<p>It turns out the trick for coaxing out these nicer error messages is to manually
desugar an <code class="language-plaintext highlighter-rouge">async fn</code> or two. In our case, manually desugaring the entry point
to the PostgreSQL protocol server from</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">fn</span> <span class="nf">handle_connection</span><span class="p">(</span><span class="n">conn</span><span class="p">:</span> <span class="n">Connection</span><span class="p">)</span> <span class="p">{</span>
<span class="c">// Do stuff with `conn`.</span>
<span class="p">}</span>
</code></pre></div></div>
<p>into</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">handle_connection</span><span class="p">(</span><span class="n">conn</span><span class="p">:</span> <span class="n">Conn</span><span class="p">)</span> <span class="k">-></span> <span class="k">impl</span> <span class="n">Future</span><span class="o"><</span><span class="n">Output</span> <span class="o">=</span> <span class="p">()</span><span class="o">></span> <span class="o">+</span> <span class="nb">Send</span> <span class="p">{</span>
<span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
<span class="c">// Do stuff with `conn`.</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>is literally all that’s required. The key is the explicit <code class="language-plaintext highlighter-rouge">Send</code> bound that
we’ve added to the return type.</p>
<p>Here’s the fantastic error that the compiler spits out now:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error: future cannot be sent between threads safely
--> src/pgwire/src/protocol.rs:93:10
|
93 | ) -> impl Future<Output = Result<(), comm::Error>> + Send {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future created by async block is not `Send`
|
= help: the trait `std::marker::Sync` is not implemented for `(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)`
note: future is not `Send` as this value is used across an await
--> src/pgwire/src/protocol.rs:537:9
|
525 | let portal = match self.coord_client.session().get_portal(&name) {
| ------ has type `&coord::session::Portal` which is not `Send`
...
537 | self.send_describe_rows(stmt_name).await
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ await occurs here, with `portal` maybe used later
538 | }
| - `portal` is later dropped here
= note: the return type of a function must have a statically known size
</code></pre></div></div>
<p>With this laser-focused error message, the problem is now clear. There is a
completely unrelated part of the protocol server, far away from <code class="language-plaintext highlighter-rouge">execute_rows</code>,
that acquires a reference to a portal and inadvertently holds that reference
across an await point. Since the <code class="language-plaintext highlighter-rouge">Portal</code> type is no longer <code class="language-plaintext highlighter-rouge">Sync</code> due to our
changes, holding a reference to a portal across an await point is no longer
permissible.</p>
<p>The fix is thankfully trivial, since the offending code can easily be refactored
to scope the borrow of <code class="language-plaintext highlighter-rouge">portal</code> more tightly so that it is not alive across an
await point.</p>
<h2 id="whats-going-on">What’s going on?</h2>
<p>As to why this trick makes the error message so much better, I’m not entirely
sure. If I had to guess, the heuristics for when the simplified error message
applies are not perfect. Somehow being explicit about <code class="language-plaintext highlighter-rouge">Send</code> bounds more often
helps these heuristics along.</p>
<p>In Materialize in particular, the function that calls <code class="language-plaintext highlighter-rouge">handle_connection</code> does
so by way of the <a href="https://github.com/dtolnay/async-trait">async-trait</a> crate. There’s a decent bit of magic involved,
since proper async traits don’t yet exist in the language. So possibly that is
what is impeding the heuristics.</p>
<p>Particularly confusing is the fact that the original error message was
complaining about a future not implementing <em><code class="language-plaintext highlighter-rouge">Sync</code></em> but the solution is to add
a <em><code class="language-plaintext highlighter-rouge">Send</code></em> bound. But look closely at the original error message and it will
make sense in hindsight:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> = help: the trait `std::marker::Sync` is not implemented for `(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)`
= note: required because of the requirements on the impl of `std::marker::Sync` for `std::ptr::Unique<(dyn futures::Stream<Item = std::result::Result<std::vec::Vec<repr::Row>, comm::Error>> + std::marker::Send + std::marker::Unpin + 'static)>`
...
= note: required for the cast to the object type `dyn futures::Future<Output = std::result::Result<(), anyhow::Error>> + std::marker::Send`
</code></pre></div></div>
<p>The notes indicate that the outer future must implement <code class="language-plaintext highlighter-rouge">Send</code>, but it cannot
because there is an inner future which is not <code class="language-plaintext highlighter-rouge">Sync</code>. (The part left unsaid,
which we learned from the improved error message, is that the inner future only
needs to be <code class="language-plaintext highlighter-rouge">Sync</code> because it is unnecessarily held across an await point.)</p>
<p>If anyone reading this can offer more clues about what is going on, I’d love to
file an issue upstream—or, better yet, a pull request. As it stands, I’ve been
unable to reduce the reproduction to anything less than “all of Materialize”,
which I don’t feel is appropriate for a bug report.</p>
<p>For now, this weird trick will have to suffice.<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote">3</a></sup></p>
<h2 id="extra-credit">Extra credit</h2>
<p>If you look at the actual protocol code, rather than the simplified code in the
blog post, you’ll notice something strange. According to the rules of
<a href="https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md">non-lexical lifetimes</a> (NLL), the lifetime of the borrow of the portal
doesn’t span the await point! Here’s a verbatim reproduction of the code
implicated by the improved error message:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">portal</span> <span class="o">=</span> <span class="k">match</span> <span class="k">self</span><span class="py">.coord_client</span><span class="nf">.session</span><span class="p">()</span><span class="nf">.get_portal</span><span class="p">(</span><span class="o">&</span><span class="n">name</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">Some</span><span class="p">(</span><span class="n">portal</span><span class="p">)</span> <span class="k">=></span> <span class="n">portal</span><span class="p">,</span>
<span class="nb">None</span> <span class="k">=></span> <span class="p">{</span>
<span class="k">return</span> <span class="k">self</span>
<span class="nf">.error</span><span class="p">(</span>
<span class="nn">SqlState</span><span class="p">::</span><span class="n">INVALID_SQL_STATEMENT_NAME</span><span class="p">,</span>
<span class="s">"portal does not exist"</span><span class="p">,</span>
<span class="p">)</span>
<span class="py">.await</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="k">let</span> <span class="n">stmt_name</span> <span class="o">=</span> <span class="n">portal</span><span class="py">.statement_name</span><span class="nf">.clone</span><span class="p">();</span>
<span class="c">// the lifetime of the portal borrow ends here</span>
<span class="k">self</span><span class="nf">.send_describe_rows</span><span class="p">(</span><span class="n">stmt_name</span><span class="p">)</span><span class="py">.await</span>
</code></pre></div></div>
<p>By the time we get to the last line, we’ve cloned what we need out of the
portal, so the lifetime of the portal borrow needn’t include the last line of
the function.</p>
<p>Unfortunately, async/await generators do not use the same NLL analysis that the
borrow checker does when determining what variables are alive across an await
point. Instead, the generators use a much coarser analysis based on variable
scope, like the old borrow checker. Since the scope of the variable named
<code class="language-plaintext highlighter-rouge">portal</code> includes the last line of the function, the compiler considers its
contents to be held across the await point.</p>
<p>If you want to experience this for yourself, I’ve reduced this behavior to a
very simple snippet. Simply assigning an <a href="https://doc.rust-lang.org/std/rc/struct.Rc.html"><code class="language-plaintext highlighter-rouge">Rc</code></a> to a variable in a scope that
later calls <code class="language-plaintext highlighter-rouge">await</code> fails to compile, even if you immediately drop the <code class="language-plaintext highlighter-rouge">Rc</code>:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">sendable</span><span class="p">()</span> <span class="k">-></span> <span class="k">impl</span> <span class="n">Future</span><span class="o"><</span><span class="n">Output</span> <span class="o">=</span> <span class="nb">i64</span><span class="o">></span> <span class="o">+</span> <span class="nb">Send</span> <span class="p">{</span>
<span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">x</span> <span class="o">=</span> <span class="nn">Rc</span><span class="p">::</span><span class="nf">new</span><span class="p">(());</span>
<span class="k">drop</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
<span class="nf">useless</span><span class="p">()</span><span class="py">.await</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">useless</span><span class="p">()</span> <span class="k">-></span> <span class="nb">i64</span> <span class="p">{</span>
<span class="mi">42</span>
<span class="p">}</span>
</code></pre></div></div>
<p><a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8f55a7d4fa9055131bcb8fa4940112ec">Try it on the Rust playground.</a></p>
<p>There has been much discussion about fixing this in <a href="https://github.com/rust-lang/rust/issues/57017">rust-lang/rust#57017</a>, but
for many reasons that are above my pay grade to explain, the fix is extremely
difficult.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p><a href="https://materialize.io">Materialize</a> is a streaming SQL database. The
company behind this product employs both me and Matt. Materialize pretends
to be a PostgreSQL server so that tools written for PostgreSQL mostly just
work if you point them at a Materialize server instead. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>The reason for the “portal” concept is to allow a single connection to
execute multiple queries concurrently. A client can establish multiple
portals and then round-robin execute requests across the portals.
I’m not actually sure when this is useful, but it is supported by
PostgreSQL, and therefore also supported by Materialize. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>To be explicit, I’m very grateful for the existence of improved the error
message, even though it doesn’t fire 100% of the time. We’d still be
scratching our heads about this issue if there weren’t <em>some</em> way of
coaxing a better error message out of the compiler. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Nikhil BeneschAsync/await support in Rust may be stable, but sufficiently complicated programs will generate errors that put C++ to shame.Gccgo in 20192019-01-16T00:00:00+00:002019-01-16T00:00:00+00:00https://meltware.com/2019/01/16/gccgo-benchmarks-2019<h2 id="the-past">The past</h2>
<p>Back in 2013, when Go 1.2 was on the cusp of release, Dave Cheney <a href="https://dave.cheney.net/2013/11/19/benchmarking-go-1-2rc5-vs-gccgo">benchmarked
gccgo</a> against the standard Go compiler, gc. The results were rather
disappointing. Code produced by gccgo was much slower than code produced by gc
for all but the most CPU bound of workloads.</p>
<p>It’s been five years, and much has changed. It’s high time for these benchmarks
to be updated for the Go 1.11 era. Gccgo has learned some new tricks, like
escape analysis, but gc has seen a continual stream of improvements, from both
the Go team and the community. (Gccgo sees activity from just a <a href="https://github.com/golang/gofrontend/graphs/contributors?from=2013-01-01&to=2019-01-15&type=c">handful of
folks</a>.)</p>
<h2 id="the-present-in-summary">The present, in summary</h2>
<p>To enable comparison with Dave’s results, I ran the same <a href="https://github.com/golang/go/tree/go1.11.4/test/bench/go1">“go1” benchmark
suite</a><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup> under Go 1.11. The results are below.</p>
<figure>
<div style="height: 650px;">
<script>renderBarChart(gccgoVsGc)</script>
</div>
<figcaption>
The graphed quantity is the percent change in each benchmark's time per
operation between when the benchmark is compiled with gc 1.11.1 and when it
is compiled with gccgo 9.0.0 20190112 (experimental). A positive percentage
change (red) indicates that compiling with gccgo yielded slower code, while
a negative percentage change (green) indicates that gccgo yielded a speedup.
</figcaption>
</figure>
<p>There’s a lot more red on the board this time around. Gccgo used to eke out a
win on a half dozen of the CPU-intensive benchmarks, but in the last five years
gc has closed the gap. The only remaining benchmark where gccgo has the upper
hand is <code class="language-plaintext highlighter-rouge">Fannkuch11</code>, and it’s a very small margin at that.</p>
<p>What’s happened is that, while both compilers are improving, gc is improving
faster than gccgo, and so gccgo looks worse in comparison. For proof, we can
compare gccgo against itself. Here’s today’s gccgo compared to gccgo 4.9:</p>
<figure>
<div style="height: 650px;">
<script>renderBarChart(gccgo49VsGccgo90)</script>
</div>
<figcaption>
Analogous to figure 1, except that the comparison is across benchmarks
compiled with gccgo 4.9.0 and gccgo 9.0.0 20190114 (experimental).
</figcaption>
</figure>
<p>This figure paints a much rosier picture. Gccgo is, indeed, improving. Today’s
version of gccgo results in double-digit improvements over 2013’s gccgo for most
go1 benchmarks. That’s actually quite the achievement, given how
resource-constrained gccgo development appears to be.</p>
<h2 id="the-present-in-detail">The present, in detail</h2>
<p>But what’s up with those six benchmarks that have gotten slower? It’s not
immediately clear whether gccgo is to blame. Since we’re comparing benchmark
results across Go versions, we’re not just measuring changes to the compiler;
we’re also measuring changes to the runtime and standard library. The
performance degradation in <code class="language-plaintext highlighter-rouge">HTTPClientServer</code>, for example, could just as easily
be the result of a change to the <code class="language-plaintext highlighter-rouge">net/http</code> package as a change to the gccgo
compiler internals.</p>
<p>In fact, it’s nearly impossible to isolate just the compiler improvements, as
each Go compiler is tightly coupled to its contemporaneous runtime and standard
library. But we can extract at least a fuller picture by comparing the evolution
of gccgo performance to the evolution of gc performance. I want to take a look
at two representative examples.</p>
<figure>
<div class="side-by-side">
<div style="height: 300px">
<script>
renderLineChart("HTTPClientServer", "µs / op", [
{ name: "gccgo", data: [{ name: "2013", y: 61.4 }, { name: "2019", y: 192.3 }], },
{ name: "gc", data: [{ name: "2013", y: 51.6 }, { name: "2019", y: 101.5 }], },
]);
</script>
</div>
<div style="height: 300px">
<script>
renderLineChart("BinaryTree17", "s / op", [
{ name: "gccgo", data: [{ name: "2013", y: 2.63 }, { name: "2019", y: 3.95 }], },
{ name: "gc", data: [{ name: "2013", y: 4.32 }, { name: "2019", y: 3.25 }], },
]);
</script>
</div>
</div>
<figcaption>
Each graph compares how performance on one benchmark has evolved in the last
five years. The blue trend line graphs gccgo performance, while the orange
trend line graphs gc performance. A positive slope indicate that the
benchmark has gotten slower over time with that compiler, while a negative
slope indicates that the benchmark has gotten faster over time with that
compiler. An intersection of trend lines indicates that the relative
performance of the compilers has reversed.
</figcaption>
</figure>
<p>It turns out that the first benchmark, <code class="language-plaintext highlighter-rouge">HTTPClientServer</code>, has gotten slower
with gc, too. As unfortunate as this is, it’s reassuring evidence that
there is nothing particularly wrong with gccgo. I suspect that a bug, or a
series of bugs, was discovered in the runtime or <code class="language-plaintext highlighter-rouge">net/http</code> whose solution(s)
forced a performance regression.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">2</a></sup> Performance regressions in the standard Go
toolchain do not go unnoticed for long, so it is likely that this regression
was intentional.</p>
<p>The results for the <code class="language-plaintext highlighter-rouge">BinaryTree17</code> benchmark, on the other hand, are downright
strange. Gc managed a nearly 25% speedup on this workload, while gccgo yielded a
50% slowdown over the same time frame. There is clearly something in gccgo to
investigate here, especially considering that the benchmark makes use of no
standard library features (<a href="https://github.com/golang/go/blob/go1.11.1/test/bench/go1/binarytree_test.go#L10">proof</a>). Since the benchmark does
depend heavily on the garbage collector and memory allocator, I suspect that
something’s gone amiss in gccgo’s runtime.</p>
<p>I’ve begun gathering a list of the <a href="https://groups.google.com/forum/#!topic/gofrontend-dev/8aoBiT_Qat8">known performance
bottlenecks</a> as a starting point for investigation of these
performance problems. If you know of additional bottlenecks, or know of code
that behaves particularly pathologically with gccgo, please chime in!</p>
<h2 id="the-upshot">The upshot</h2>
<p>In 2019, performance is still a sore spot for gccgo. Gc yields faster code than
gccgo on nearly every workload. Unless you’re compiling for an esoteric platform
that gc doesn’t support, or you need faster interop between Go and C than cgo
provides, there is little reason (yet!) to choose gccgo over the standard Go
toolchain.</p>
<p><em>Raw benchmarking data for all the figures in this post, as well as reproduction
instructions, are available as a <a href="https://gist.github.com/benesch/1701004153b2f075aaca556c26b9285b">GitHub Gist</a>.</em></p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>I’m not entirely clear on what the Go team uses the go1 benchmark suite
for, but the commit that introduced the suite (<a href="https://github.com/golang/go/commit/6e8875551a0db770c5fbaaf4c126646f1709cab1#diff-b5f16dd22d941438841ca1de776ebd13">6e88755</a>) claims the
“intent is to have mostly end-to-end benchmarks timing real world
operations,” which is exactly what we’re after. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>For a good example of how bug fixes can force performance regressions,
take a look at <a href="https://github.com/golang/go/issues/18964">golang/go#18964</a>. Note that this particular issue could
only be responsible for about 1% of the total regression observed in the
<code class="language-plaintext highlighter-rouge">HTTPClientServer</code> benchmark. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Nikhil BeneschThe last benchmarks for gccgo were published to the blogosphere in 2013. How has gccgo performance evolved in the last five years?hello, world2019-01-09T00:00:00+00:002019-01-09T00:00:00+00:00https://meltware.com/2019/01/09/hello-world<p>One of my New Year’s resolutions for 2018 was to start blogging. Thirteen months
later I’m making good on that resolution. You’ll have to forgive my tardiness.</p>
<p>I’m just old enough to remember when the internet was a smaller place. I learned
HTML and CSS from some guy named Dave, who ran the eponymous <a href="http://davesite.com">davesite.com</a>,<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup>
back when that was a cool domain name. I had a Geocities account, though I’m
sorry to say that the URL has been lost to the sands of time.<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">2</a></sup> Back in those
days, you started a blog by FTPing some PHP files to some shared webhost and
praying that your blog got popular, but not <em>too</em> popular, because you couldn’t
figure out how to get the webserver to disable <code class="language-plaintext highlighter-rouge">register_globals</code>. And those
blogs were often pure gold, better references to delightfully obscure topics
than anything money could buy.</p>
<p>This blog is my attempt to join this storied tradition, before that old web
disappears altogether.</p>
<p>If the programmer “humor” hasn’t made it clear already, I write code for a
living. I spent the past two years working on <a href="https://cockroachlabs.com">CockroachDB</a>, a distributed,
horizontally scalable, strongly consistent, cloud native SQL database. If that
doesn’t mean anything to you, just know that CockroachDB is what everyone
actually wants when they think they want “blockchain.”</p>
<p>I’m now on sabbatical at the <a href="https://www.recurse.com">Recurse Center</a>, where I’ll be spending
the next six weeks following my programming whims to sort out what I want to do
next.</p>
<p>I expect this blog to be mostly software engineering war stories. I’ve managed
to accumulate quite the collection of weird facts and crazy bugs. Every now and
then I might slip up and write something off topic or personal. We’ll see.</p>
<p>In an effort to shrink the internet just a little bit, I want to offer a tip of
the hat to some of my colleagues who blazed the blogging trail ahead of me:</p>
<ul>
<li>
<p><a href="https://ristret.com">Arjun Narayan</a>, who’s doing double duty as a contemporary technology
philosopher and the
<a href="https://ristret.com/s/f643zk/history_transaction_histories">unofficial</a>
<a href="https://ristret.com/s/gnd4yr/brief_history_log_structured_merge_trees">historian</a>
of storage systems.</p>
</li>
<li>
<p><a href="http://justinjaffray.com">Justin Jaffray</a>, who wrote the only <a href="http://justinjaffray.com/what-does-write-skew-look-like/">understandable definition of write
skew</a> in the known universe, and beat Leslie Lamport to
actually explaining <a href="http://justinjaffray.com/why-consensus/">why consensus is important</a></p>
</li>
<li>
<p><a href="http://mattjibson.com">Matt Jibson</a> who, at least once a year, becomes the world’s expert on a new
topic. Mostly recently it’s been <a href="https://mattjibson.com/rounding/">rounding floating point numbers in
Go</a> and <a href="https://mattjibson.com/sqlfmt/">formatting SQL statements
automatically</a>.</p>
</li>
<li>
<p><a href="https://science.raphael.poss.name">Raphael ‘kena’ Poss</a>, who has written so many wonderful articles on such a
wide variety of topics I can’t even begin to describe them.</p>
</li>
</ul>
<p>There are some other programmers, whom I don’t know personally, whose blogs I
admire from afar:</p>
<ul>
<li><a href="https://research.swtch.com">Russ Cox</a></li>
<li><a href="https://airs.com/blog/">Ian Lance Taylor</a></li>
<li><a href="https://dave.cheney.net">Dave Cheney</a></li>
<li><a href="https://danluu.com">Dan Luu</a></li>
<li><a href="https://codinghorror.com">Jeff Atwood</a></li>
<li><a href="https://joelonsoftware.com">Joel Spolsky</a></li>
</ul>
<p>I’m sure I’ve forgotten folks. I’ll update the list as the names come to me.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>His site had far fewer ads in those days. Sheesh. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>If you’re looking for embarrassing relics of my internet past, I’ll leave
it to you to find my Wikipedia user page. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Nikhil BeneschOne of my New Year’s resolutions for 2018 was to start blogging. Thirteen months later I’m making good on that resolution. You’ll have to forgive my tardiness.