{"id":109468,"date":"2024-03-01T07:00:00","date_gmt":"2024-03-01T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109468"},"modified":"2024-03-01T18:15:00","modified_gmt":"2024-03-02T02:15:00","slug":"20240301-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240301-00\/?p=109468","title":{"rendered":"C++\/WinRT performance trap: Switching to Windows Runtime too soon"},"content":{"rendered":"<p>The Windows Runtime contains equivalents to C++ vectors and maps, namely <code>IVector<\/code> and <code>IMap<\/code>. If you have a choice, stick with the C++ versions, and if you have to produce a Windows Runtime version, delay the conversion as long as possible.<\/p>\n<p>The reason is that the Windows Runtime types are all virtual interfaces, which means vtable calls for all the methods which cannot be inlined or optimized. Whereas C++ library types have a richer set of available methods to allow you to write simpler code, and since they are written in C++ itself, the compiler can perform optimizations that aren&#8217;t available to virtual methods.<\/p>\n<p>For example, suppose you have a method that has to return an <code>IVector&lt;Widget&gt;<\/code>. I see a lot of people write it like this:<\/p>\n<pre>winrt::IVector&lt;Widget&gt; GetWidgets()\r\n{\r\n    winrt::IVector&lt;Widget&gt; widgets =\r\n        winrt::multi_threaded_vector&lt;Widget&gt;();\r\n\r\n    widgets.Append(GetFirstWidget());\r\n    widgets.Append(GetSecondWidget());\r\n    widgets.Append(GetThirdWidget());\r\n\r\n    return widgets;\r\n}\r\n<\/pre>\n<p>This creates an empty Windows Runtime vector and adds three widgets to it. Each of those <code>Append<\/code> calls is a virtual method call that the compiler may not be able to devirtualize, and since you have a multi-threaded vector, it&#8217;s going to do some locking to protect against concurrent access, even though no concurrency is possible here because the vector hasn&#8217;t been shared with anyone else yet.<\/p>\n<p>More efficient would be<\/p>\n<pre>winrt::IVector&lt;Widget&gt; GetWidgets()\r\n{\r\n    std::vector&lt;Widget&gt; widgets;\r\n    widgets.emplace_back(GetFirstWidget());\r\n    widgets.emplace_back(GetSecondWidget());\r\n    widgets.emplace_back(GetThirdWidget());\r\n\r\n    return winrt::multi_threaded_vector&lt;Widget&gt;(\r\n        std::move(widgets));\r\n}\r\n<\/pre>\n<p>Creating the vector is done in the C++ world, where the compiler can do all sorts of clever optimizations. Only at the end is this vector converted to a Windows Runtime vector to be returned to the caller.<\/p>\n<p>And now that you&#8217;ve gotten the vector-building in C++, you can take advantage of additional C++ features, like reservation to avoid reallocation:<\/p>\n<pre>winrt::IVector&lt;Widget&gt; GetWidgets()\r\n{\r\n    std::vector&lt;Widget&gt; widgets;\r\n    <span style=\"border: solid 1px currentcolor;\">widgets.reserve(3);<\/span>\r\n\r\n    widgets.emplace_back(GetFirstWidget());\r\n    widgets.emplace_back(GetSecondWidget());\r\n    widgets.emplace_back(GetThirdWidget());\r\n\r\n    return winrt::multi_threaded_vector&lt;Widget&gt;(\r\n        std::move(widgets));\r\n}\r\n<\/pre>\n<p>Or construction from an initializer-list.<\/p>\n<pre>winrt::IVector&lt;Widget&gt; GetWidgets()\r\n{\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">std::vector&lt;Widget&gt; widgets{<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    GetFirstWidget(),       <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    GetSecondWidget(),      <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    GetThirdWidget(),       <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">};                          <\/span>\r\n\r\n    return winrt::multi_threaded_vector(\r\n        std::move(widgets));\r\n}\r\n<\/pre>\n<p><b>Bonus reading<\/b>: <a title=\"On the virtues of the trailing comma\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240209-00\/?p=109379\"> On the virtues of the trailing comma<\/a>.<\/p>\n<p>Once we construct the vector from an initializer-list, we can let CTAD save us some more typing:<\/p>\n<pre>winrt::IVector&lt;Widget&gt; GetWidgets()\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">std::vector<\/span> widgets{\r\n        GetFirstWidget(),\r\n        GetSecondWidget(),\r\n        GetThirdWidget(),\r\n    };\r\n\r\n    return winrt::multi_threaded_vector(\r\n        std::move(widgets));\r\n}\r\n<\/pre>\n<p>And finally, we can just in-place construct the vector in the parameter list for <code>multi_<wbr \/>threaded_<wbr \/>vector<\/code>.<\/p>\n<pre>winrt::IVector&lt;Widget&gt; GetWidgets()\r\n{\r\n    return winrt::multi_threaded_vector(\r\n        <span style=\"border: solid 1px currentcolor;\">std::vector<\/span>{\r\n            GetFirstWidget(),\r\n            GetSecondWidget(),\r\n            GetThirdWidget(),\r\n        });\r\n}\r\n<\/pre>\n<p>A similar shortcut applies to maps.<\/p>\n<pre>winrt::IMap&lt;winrt::hstring, int32_t&gt; GetCounts()\r\n{\r\n    return winrt::multi_threaded_map(\r\n        std::unordered_map&lt;winrt::hstring, int32_t&gt;{\r\n            { L\"triangles\", GetTriangleCount() },\r\n            { L\"rectangles\", GetCircleCount() },\r\n            { L\"circles\", GetCircleCount() },\r\n        });\r\n}\r\n<\/pre>\n<p>Even if you can&#8217;t reduce your function to a one-liner, it&#8217;s more efficient (and probably a lot easier) to collect the contents into a C++ vector or map (or unordered map) and then wrap it inside a Windows Runtime interface as a final step.<\/p>\n<pre>winrt::IVector&lt;Widget&gt; GetWidgets()\r\n{\r\n    std::vector&lt;Widget&gt; widgets;\r\n\r\n    \/\/ Collect all the widgets from enabled doodads\r\n    for (auto&amp;&amp; doodad : m_doodads) {\r\n        if (doodad.m_enabled) {\r\n            widgets.insert(widgets.end(),\r\n                doodad.m_widgets.begin(),\r\n                doodad.m_widgets.end());\r\n        }\r\n    }\r\n\r\n    \/\/ Sort by population descending\r\n    std::sort(widgets.begin(), widgets.end(),\r\n        [](Widget const&amp; left, Widget const&amp; right) {\r\n            return left.Population() &gt; right.Population();\r\n        });\r\n\r\n    return winrt::multi_threaded_vector(\r\n        std::move(widgets));\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Stay in the devirtualized world until you are forced to leave.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109468","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Stay in the devirtualized world until you are forced to leave.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109468","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109468"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109468\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109468"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109468"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109468"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}