A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 52KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Speed up your Python using Rust (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://developers.redhat.com/blog/2017/11/16/speed-python-using-rust/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. Speed up your Python using Rust (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://developers.redhat.com/blog/2017/11/16/speed-python-using-rust/">Source originale du contenu</a></h3>
  445. <h2>What is Rust?</h2>
  446. <p><strong>Rust</strong> is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.</p>
  447. <p><strong>Featuring</strong></p>
  448. <ul>
  449. <li>zero-cost abstractions</li>
  450. <li>move semantics</li>
  451. <li>guaranteed memory safety</li>
  452. <li>threads without data races</li>
  453. <li>trait-based generics</li>
  454. <li>pattern matching</li>
  455. <li>type inference</li>
  456. <li>minimal runtime</li>
  457. <li>efficient C bindings</li>
  458. </ul>
  459. <p>Description is taken from <a href="http://rust-lang.org">rust-lang.org</a>.</p>
  460. <h2><a id="user-content-why-does-it-matter-for-a-python-developer" class="anchor" href="https://github.com/rochacbruno/rust-python-example#why-does-it-matter-for-a-python-developer"></a>Why does it matter for a Python developer?</h2>
  461. <p>The better description of Rust I heard from <a href="https://github.com/dlight"><strong>Elias</strong></a> (a member of the <a href="https://t.me/rustlangbr"><strong>Rust Brazil Telegram Group)</strong></a>.</p>
  462. <blockquote><p><strong>Rust is</strong> a language that allows you to build high level abstractions, but without giving up low-level control &#8211; that is, control of how data is represented in memory, control of which threading model you want to use etc.<br />
  463. <strong>Rust is</strong> a language that can usually detect, during compilation, the worst parallelism and memory management errors (such as accessing data on different threads without synchronization, or using data after they have been deallocated), but gives you a hatch escape in the case you really know what you&#8217;re doing.<br />
  464. <strong>Rust is</strong> a language that, because it has no runtime, can be used to integrate with any runtime; you can write a native extension in Rust that is called by a program node.js, or by a python program, or by a program in ruby, lua etc. and, however, you can script a program in Rust using these languages. &#8212; &#8220;Elias Gabriel Amaral da Silva&#8221;</p></blockquote>
  465. <p>There is a bunch of Rust packages out there to help you extending Python with Rust.</p>
  466. <p>I can mention <a href="https://github.com/getsentry/milksnake">Milksnake</a> created by Armin Ronacher (the creator of Flask) and also <a href="https://github.com/PyO3/pyo3">PyO3</a> The Rust bindings for Python interpreter.</p>
  467. <h3><em>See a complete reference list at the bottom of this article.</em></h3>
  468. <p>Let&#8217;s<strong> see it in action</strong></p>
  469. <p>For this post, I am going to use <a href="https://github.com/dgrunwald/rust-cpython">Rust Cpython</a>, it&#8217;s the only one I have tested, it is compatible with stable version of Rust and found it straightforward to use.</p>
  470. <blockquote><p><strong>NOTE</strong>: <a href="https://github.com/PyO3/pyo3">PyO3</a> is a fork of rust-cpython, comes with many improvements, but works only with the nightly version of Rust, so I prefered to use the stable for this post, anyway the examples here must work also with PyO3.</p></blockquote>
  471. <p><strong>Pros:</strong> It is easy to write Rust functions and import from Python and as you will see by the benchmarks it worth in terms of performance.</p>
  472. <p><strong>Cons:</strong> The distribution of your <strong>project/lib/framework</strong> will demand the Rust module to be compiled on the target system because of variation of environment and architecture, there will be a <strong>compiling</strong> stage which you don&#8217;t have when installing Pure Python libraries, you can make it easier using <a href="https://pypi.python.org/pypi/setuptools-rust">rust-setuptools</a> or using the <a href="https://github.com/getsentry/milksnake">MilkSnake</a> to embed binary data in Python Wheels.</p>
  473. <h2><a id="user-content-python-is-sometimes-slow" class="anchor" href="https://github.com/rochacbruno/rust-python-example#python-is-sometimes-slow"></a>Python is sometimes slow</h2>
  474. <p>Yes, Python is known for being &#8220;slow&#8221; in some cases and the good news is that this doesn&#8217;t really matter depending on your project goals and priorities. For most projects, this detail will not be very important.</p>
  475. <p>However, you may face the <strong>rare</strong> case where a single function or module is taking too much time and is detected as the bottleneck of your project performance, often happens with string parsing and image processing.</p>
  476. <h2><a id="user-content-example" class="anchor" href="https://github.com/rochacbruno/rust-python-example#example"></a>Example</h2>
  477. <p>Let&#8217;s say you have a Python function which does a string processing, take the following easy example of <code>counting pairs of repeated chars</code>, but have in mind that this example can be reproduced with other <code>string processing</code> functions or any other generally slow process in Python.</p>
  478. <div class="highlight highlight-source-shell">
  479. <pre><span class="pl-c"># How many subsequent-repeated group of chars are in the given string? </span>
  480. abCCdeFFghiJJklmnopqRRstuVVxyZZ... {millions of chars here}
  481. 1 2 3 4 5 6</pre>
  482. </div>
  483. <p>Python is slow for doing large <code>string</code> processing, so you can use <code>pytest-benchmark</code> to compare a <code>Pure Python (with Iterator Zipping)</code> function versus a <code>Regexp</code> implementation.</p>
  484. <div class="highlight highlight-source-shell">
  485. <pre><span class="pl-c"># Using a Python3.6 environment</span>
  486. $ pip3 install pytest pytest-benchmark
  487. </pre>
  488. </div>
  489. <p>Then write a new Python program called <code>doubles.py</code></p>
  490. <div class="highlight highlight-source-python">
  491. <pre><span class="pl-k">import</span> re
  492. <span class="pl-k">import</span> string
  493. <span class="pl-k">import</span> random
  494. <span class="pl-c"># Python ZIP version</span>
  495. <span class="pl-k">def</span> <span class="pl-en">count_doubles</span>(<span class="pl-smi">val</span>):
  496. total <span class="pl-k">=</span> <span class="pl-c1">0
  497. </span> # there is an improved version later on this post
  498. <span class="pl-k">for</span> c1, c2 <span class="pl-k">in</span> <span class="pl-c1">zip</span>(val, val[<span class="pl-c1">1</span>:]):
  499. <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2:
  500. total <span class="pl-k">+=</span> <span class="pl-c1">1</span>
  501. <span class="pl-k">return</span> total
  502. <span class="pl-c"># Python REGEXP version</span>
  503. double_re <span class="pl-k">=</span> re.compile(<span class="pl-sr"><span class="pl-k">r</span><span class="pl-pds">'</span><span class="pl-k">(?=</span>(<span class="pl-c1">.</span>)<span class="pl-ent">\1</span>)<span class="pl-pds">'</span></span>)
  504. <span class="pl-k">def</span> <span class="pl-en">count_doubles_regex</span>(<span class="pl-smi">val</span>):
  505. <span class="pl-k">return</span> <span class="pl-c1">len</span>(double_re.findall(val))
  506. <span class="pl-c"># Benchmark it</span>
  507. <span class="pl-c"># generate 1M of random letters to test it</span>
  508. val <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span><span class="pl-pds">'</span></span>.join(random.choice(string.ascii_letters) <span class="pl-k">for</span> i <span class="pl-k">in</span> <span class="pl-c1">range</span>(<span class="pl-c1">1000000</span>))
  509. <span class="pl-k">def</span> <span class="pl-en">test_pure_python</span>(<span class="pl-smi">benchmark</span>):
  510. benchmark(count_doubles, val)
  511. <span class="pl-k">def</span> <span class="pl-en">test_regex</span>(<span class="pl-smi">benchmark</span>):
  512. benchmark(count_doubles_regex, val)</pre>
  513. </div>
  514. <p>Run <strong>pytest</strong> to compare:</p>
  515. <div class="highlight highlight-source-shell">
  516. <pre>$ pytest doubles.py
  517. =============================================================================
  518. platform linux -- Python 3.6.0, pytest-3.2.3, py-1.4.34, pluggy-0.4.
  519. benchmark: 3.1.1 (defaults: timer=time.perf_counter disable_gc=False min_roun
  520. rootdir: /Projects/rustpy, inifile:
  521. plugins: benchmark-3.1.1
  522. collected 2 items
  523. doubles.py ..
  524. -----------------------------------------------------------------------------
  525. Name (time <span class="pl-k">in</span> ms) Min Max Mean
  526. -----------------------------------------------------------------------------
  527. test_regex 24.6824 (1.0) 32.3960 (1.0) 27.0167 (1.0)
  528. test_pure_python 51.4964 (2.09) 62.5680 (1.93) 52.8334 (1.96)
  529. -----------------------------------------------------------------------------
  530. </pre>
  531. </div>
  532. <p>Lets take the <code>Mean</code> for comparison:</p>
  533. <ul>
  534. <li><strong>Regexp</strong> &#8211; 27.0167 <strong>&lt;&#8211; less is better</strong></li>
  535. <li><strong>Python Zip</strong> &#8211; 52.8334</li>
  536. </ul>
  537. <h1><a id="user-content-extending-python-with-rust" class="anchor" href="https://github.com/rochacbruno/rust-python-example#extending-python-with-rust"></a>Extending Python with Rust</h1>
  538. <h2><a id="user-content-create-a-new-crate" class="anchor" href="https://github.com/rochacbruno/rust-python-example#create-a-new-crate"></a>Create a new crate</h2>
  539. <blockquote><p><strong>crate</strong> is how we call Rust Packages.</p></blockquote>
  540. <p>Having rust installed (recommended way is <a href="https://www.rustup.rs/">https://www.rustup.rs/</a>) Rust is also available on Fedora and RHEL repositories by the <a href="https://developers.redhat.com/blog/2017/11/01/getting-started-rust-toolset-rhel/">rust-toolset</a></p>
  541. <blockquote><p>I used <code>rustc 1.21.0</code></p></blockquote>
  542. <p>In the same folder run:</p>
  543. <div class="highlight highlight-source-shell">
  544. <pre>cargo new pyext-myrustlib</pre>
  545. </div>
  546. <p>It creates a new Rust project in that same folder called <code>pyext-myrustlib</code> containing the <code>Cargo.toml</code> (cargo is the Rust package manager) and also a <code>src/lib.rs</code> (where we write our library implementation).</p>
  547. <h2><a id="user-content-edit-cargotoml" class="anchor" href="https://github.com/rochacbruno/rust-python-example#edit-cargotoml"></a>Edit Cargo.toml</h2>
  548. <p>It will use the <code>rust-cpython</code> crate as dependency and tell cargo to generate a <code>dylib</code> to be imported from Python.</p>
  549. <div class="highlight highlight-source-toml">
  550. <pre>[<span class="pl-en">package</span>]
  551. <span class="pl-smi">name</span> = <span class="pl-s"><span class="pl-pds">"</span>pyext-myrustlib<span class="pl-pds">"</span></span>
  552. <span class="pl-smi">version</span> = <span class="pl-s"><span class="pl-pds">"</span>0.1.0<span class="pl-pds">"</span></span>
  553. <span class="pl-smi">authors</span> = [<span class="pl-s"><span class="pl-pds">"</span>Bruno Rocha &lt;rochacbruno@gmail.com&gt;<span class="pl-pds">"</span></span>]
  554. [<span class="pl-en">lib</span>]
  555. <span class="pl-smi">name</span> = <span class="pl-s"><span class="pl-pds">"</span>myrustlib<span class="pl-pds">"</span></span>
  556. <span class="pl-smi">crate-type</span> = [<span class="pl-s"><span class="pl-pds">"</span>dylib<span class="pl-pds">"</span></span>]
  557. [<span class="pl-en">dependencies</span>.<span class="pl-en">cpython</span>]
  558. <span class="pl-smi">version</span> = <span class="pl-s"><span class="pl-pds">"</span>0.1<span class="pl-pds">"</span></span>
  559. <span class="pl-smi">features</span> = [<span class="pl-s"><span class="pl-pds">"</span>extension-module<span class="pl-pds">"</span></span>]</pre>
  560. </div>
  561. <h2><a id="user-content-edit-srclibrs" class="anchor" href="https://github.com/rochacbruno/rust-python-example#edit-srclibrs"></a>Edit src/lib.rs</h2>
  562. <p>What we need to do:</p>
  563. <ol>
  564. <li>Import all macros from <code>cpython</code> crate.</li>
  565. <li>Take <code>Python</code> and <code>PyResult</code> types from CPython into our lib scope.</li>
  566. <li>Write the <code>count_doubles</code> function implementation in <code>Rust</code>, note that this is very similar to the Pure Python version except for:
  567. <ul>
  568. <li>It takes a <code>Python</code> as first argument, which is a reference to the Python Interpreter and allows Rust to use the <code>Python GIL</code>.</li>
  569. <li>Receives a <code>&amp;str</code> typed <code>val</code> as reference.</li>
  570. <li>Returns a <code>PyResult</code> which is a type that allows the rise of Python exceptions.</li>
  571. <li>Returns an <code>PyResult</code> object in <code>Ok(total)</code> (<strong>Result</strong> is an enum type that represents either success (Ok) or failure (Err)) and as our function is expected to return a <code>PyResult</code> the compiler will take care of <strong>wrapping</strong> our <code>Ok</code> on that type. (note that our PyResult expects a <code>u64</code> as return value).</li>
  572. </ul>
  573. </li>
  574. <li>Using <code>py_module_initializer!</code> macro we register new attributes to the lib, including the <code>__doc__</code> and also we add the <code>count_doubles</code> attribute referencing our <code>Rust implementation of the function</code>.
  575. <ul>
  576. <li>Attention to the names <strong>lib</strong>myrustlib, <strong>initlib</strong>myrustlib, and <strong>PyInit.</strong></li>
  577. <li>We also use the <code>try!</code> macro, which is the equivalent to Python&#8217;s<code>try.. except</code>.</li>
  578. <li>Return <code>Ok(())</code> &#8211; The <code>()</code> is an empty result tuple, the equivalent of <code>None</code> in Python.</li>
  579. </ul>
  580. </li>
  581. </ol>
  582. <div class="highlight highlight-source-rust">
  583. <pre>#[macro_use]
  584. <span class="pl-k">extern</span> <span class="pl-k">crate</span> cpython;
  585. <span class="pl-k">use</span> cpython<span class="pl-k">::</span>{Python, PyResult};
  586. <span class="pl-k">fn</span> <span class="pl-en">count_doubles</span>(_py: Python, val: <span class="pl-k">&amp;</span><span class="pl-k">str</span>) -&gt; PyResult&lt;<span class="pl-k">u64</span>&gt; {
  587. <span class="pl-k">let</span> <span class="pl-k">mut</span> total <span class="pl-k">=</span> <span class="pl-c1">0u64</span>;
  588. // There is an improved version later on this post
  589. <span class="pl-k">for</span> (c1, c2) <span class="pl-k">in</span> val.<span class="pl-en">chars</span>().<span class="pl-en">zip</span>(val.<span class="pl-en">chars</span>().<span class="pl-en">skip</span>(<span class="pl-c1">1</span>)) {
  590. <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2 {
  591. total <span class="pl-k">+=</span> <span class="pl-c1">1</span>;
  592. }
  593. }
  594. <span class="pl-c1">Ok</span>(total)
  595. }
  596. <span class="pl-en">py_module_initializer!</span>(libmyrustlib, initlibmyrustlib, PyInit_myrustlib, <span class="pl-k">|</span>py, m <span class="pl-k">|</span> {
  597. <span class="pl-c1">try!</span>(m.<span class="pl-en">add</span>(py, <span class="pl-s">"__doc__"</span>, <span class="pl-s">"This module is implemented in Rust"</span>));
  598. <span class="pl-c1">try!</span>(m.<span class="pl-en">add</span>(py, <span class="pl-s">"count_doubles"</span>, <span class="pl-en">py_fn!</span>(py, <span class="pl-en">count_doubles</span>(val: <span class="pl-k">&amp;</span><span class="pl-k">str</span>))));
  599. <span class="pl-c1">Ok</span>(())
  600. });
  601. </pre>
  602. </div>
  603. <h2>Now let&#8217;s build it with cargo</h2>
  604. <div class="highlight highlight-source-shell">
  605. <pre>$ cargo build --release
  606. Finished release [optimized] target(s) <span class="pl-k">in</span> 0.0 secs
  607. $ ls -la target/release/libmyrustlib<span class="pl-k">*</span>
  608. target/release/libmyrustlib.d
  609. target/release/libmyrustlib.so<span class="pl-k">*</span> <span class="pl-k">&lt;</span>-- Our dylib is here</pre>
  610. </div>
  611. <p>Now let&#8217;s copy the generated <code>.so</code> lib to the same folder where our <code>doubles.py</code> is located.</p>
  612. <blockquote><p>NOTE: on <strong>Fedora</strong> you must get a <code>.so</code> in other system you may get a <code>.dylib</code> and you can rename it changing extension to <code>.so</code>.</p></blockquote>
  613. <div class="highlight highlight-source-shell">
  614. <pre>$ <span class="pl-c1">cd</span> ..
  615. $ ls
  616. doubles.py pyext-myrustlib/
  617. $ cp pyext-myrustlib/target/release/libmyrustlib.so myrustlib.so
  618. $ ls
  619. doubles.py myrustlib.so pyext-myrustlib/</pre>
  620. </div>
  621. <blockquote><p>Having the <code>myrustlib.so</code> in the same folder or added to your Python path allows it to be directly imported, transparently as it was a Python module.</p>
  622. <p>&nbsp;</p></blockquote>
  623. <h2><a id="user-content-importing-from-python-and-comparing-the-results" class="anchor" href="https://github.com/rochacbruno/rust-python-example#importing-from-python-and-comparing-the-results"></a>Importing from Python and comparing the results</h2>
  624. <p>Edit your <code>doubles.py</code> now importing our <code>Rust implemented</code> version and adding a <code>benchmark</code> for it.</p>
  625. <div class="highlight highlight-source-python">
  626. <pre><span class="pl-k">import</span> re
  627. <span class="pl-k">import</span> string
  628. <span class="pl-k">import</span> random
  629. <span class="pl-k">import</span> myrustlib <span class="pl-c"># &lt;-- Import the Rust implemented module (myrustlib.so)</span>
  630. <span class="pl-k">def</span> <span class="pl-en">count_doubles</span>(<span class="pl-smi">val</span>):
  631. <span class="pl-s"><span class="pl-pds">"""</span>Count repeated pair of chars ins a string<span class="pl-pds">"""</span></span>
  632. total <span class="pl-k">=</span> <span class="pl-c1">0</span>
  633. <span class="pl-k">for</span> c1, c2 <span class="pl-k">in</span> <span class="pl-c1">zip</span>(val, val[<span class="pl-c1">1</span>:]):
  634. <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2:
  635. total <span class="pl-k">+=</span> <span class="pl-c1">1</span>
  636. <span class="pl-k">return</span> total
  637. double_re <span class="pl-k">=</span> re.compile(<span class="pl-sr"><span class="pl-k">r</span><span class="pl-pds">'</span><span class="pl-k">(?=</span>(<span class="pl-c1">.</span>)<span class="pl-ent">\1</span>)<span class="pl-pds">'</span></span>)
  638. <span class="pl-k">def</span> <span class="pl-en">count_doubles_regex</span>(<span class="pl-smi">val</span>):
  639. <span class="pl-k">return</span> <span class="pl-c1">len</span>(double_re.findall(val))
  640. val <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span><span class="pl-pds">'</span></span>.join(random.choice(string.ascii_letters) <span class="pl-k">for</span> i <span class="pl-k">in</span> <span class="pl-c1">range</span>(<span class="pl-c1">1000000</span>))
  641. <span class="pl-k">def</span> <span class="pl-en">test_pure_python</span>(<span class="pl-smi">benchmark</span>):
  642. benchmark(count_doubles, val)
  643. <span class="pl-k">def</span> <span class="pl-en">test_regex</span>(<span class="pl-smi">benchmark</span>):
  644. benchmark(count_doubles_regex, val)
  645. <span class="pl-k">def</span> <span class="pl-en">test_rust</span>(<span class="pl-smi">benchmark</span>): <span class="pl-c"># &lt;-- Benchmark the Rust version</span>
  646. benchmark(myrustlib.count_doubles, val)
  647. </pre>
  648. </div>
  649. <h1>Benchmark</h1>
  650. <div class="highlight highlight-source-shell">
  651. <pre>$ pytest doubles.py
  652. ==============================================================================
  653. platform linux -- Python 3.6.0, pytest-3.2.3, py-1.4.34, pluggy-0.4.
  654. benchmark: 3.1.1 (defaults: timer=time.perf_counter disable_gc=False min_round
  655. rootdir: /Projects/rustpy, inifile:
  656. plugins: benchmark-3.1.1
  657. collected 3 items
  658. doubles.py ...
  659. -----------------------------------------------------------------------------
  660. Name (time <span class="pl-k">in</span> ms) Min Max Mean
  661. -----------------------------------------------------------------------------
  662. test_rust 2.5555 (1.0) 2.9296 (1.0) 2.6085 (1.0)
  663. test_regex 25.6049 (10.02) 27.2190 (9.29) 25.8876 (9.92)
  664. test_pure_python 52.9428 (20.72) 56.3666 (19.24) 53.9732 (20.69)
  665. -----------------------------------------------------------------------------</pre>
  666. </div>
  667. <p>Lets take the <code>Mean</code> for comparison:</p>
  668. <ul>
  669. <li><strong>Rust</strong> &#8211; 2.6085 <strong>&lt;&#8211; less is better</strong></li>
  670. <li><strong>Regexp</strong> &#8211; 25.8876</li>
  671. <li><strong>Python Zip</strong> &#8211; 53.9732</li>
  672. </ul>
  673. <p>Rust implementation can be <strong>10x</strong> faster than Python Regex and <strong>21x</strong> faster than Pure Python Version.</p>
  674. <blockquote><p>Interesting that <strong>Regex</strong> version is only 2x faster than Pure Python 🙂</p></blockquote>
  675. <blockquote><p>NOTE: That numbers makes sense only for this particular scenario, for other cases that comparison may be different.</p></blockquote>
  676. <h1></h1>
  677. <h1>Updates and Improvements</h1>
  678. <p>After this article has been published I got some comments on <a href="https://www.reddit.com/r/Python/comments/7dct9v/use_rust_to_write_python_modules/">r/python</a> and also on <a href="https://www.reddit.com/r/rust/comments/7dctmp/red_hat_developers_blog_speed_up_your_python/">r/rust</a></p>
  679. <p>The contributions came as <a href="https://github.com/rochacbruno/rust-python-example/pulls?utf8=%E2%9C%93&amp;q=is%3Apr">Pull Requests</a> and you can send a new if you think the functions can be improved.</p>
  680. <p>Thanks to: <a href="https://github.com/cuviper">Josh Stone</a> we got a better implementation for Rust which iterates the string only once and also the Python equivalent.</p>
  681. <p>Thanks to: <a href="https://github.com/purple-pixie">Purple Pixie</a> we got a Python implementation using <code>itertools</code>, however this version is not performing any better and still needs improvements.</p>
  682. <h2><a id="user-content-iterating-only-once" class="anchor" href="https://github.com/rochacbruno/rust-python-example#iterating-only-once"></a>Iterating only once</h2>
  683. <div class="highlight highlight-source-rust">
  684. <pre><span class="pl-k">fn</span> <span class="pl-en">count_doubles_once</span>(_py: Python, val: <span class="pl-k">&amp;</span><span class="pl-k">str</span>) -&gt; PyResult&lt;<span class="pl-k">u64</span>&gt; {
  685. <span class="pl-k">let</span> <span class="pl-k">mut</span> total <span class="pl-k">=</span> <span class="pl-c1">0u64</span>;
  686. <span class="pl-k">let</span> <span class="pl-k">mut</span> chars <span class="pl-k">=</span> val.<span class="pl-en">chars</span>();
  687. <span class="pl-k">if</span> <span class="pl-k">let</span> <span class="pl-c1">Some</span>(<span class="pl-k">mut</span> c1) <span class="pl-k">=</span> chars.<span class="pl-en">next</span>() {
  688. <span class="pl-k">for</span> c2 <span class="pl-k">in</span> chars {
  689. <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2 {
  690. total <span class="pl-k">+=</span> <span class="pl-c1">1</span>;
  691. }
  692. c1 <span class="pl-k">=</span> c2;
  693. }
  694. }
  695. <span class="pl-c1">Ok</span>(total)
  696. }</pre>
  697. </div>
  698. <div class="highlight highlight-source-python">
  699. <pre><span class="pl-k">def</span> <span class="pl-en">count_doubles_once</span>(<span class="pl-smi">val</span>):
  700. total <span class="pl-k">=</span> <span class="pl-c1">0</span>
  701. chars <span class="pl-k">=</span> <span class="pl-c1">iter</span>(val)
  702. c1 <span class="pl-k">=</span> <span class="pl-c1">next</span>(chars)
  703. <span class="pl-k">for</span> c2 <span class="pl-k">in</span> chars:
  704. <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2:
  705. total <span class="pl-k">+=</span> <span class="pl-c1">1</span>
  706. c1 <span class="pl-k">=</span> c2
  707. <span class="pl-k">return</span> total</pre>
  708. </div>
  709. <h2><a id="user-content-python-with-itertools" class="anchor" href="https://github.com/rochacbruno/rust-python-example#python-with-itertools"></a>Python with itertools</h2>
  710. <div class="highlight highlight-source-python">
  711. <pre><span class="pl-k">import</span> itertools
  712. <span class="pl-k">def</span> <span class="pl-en">count_doubles_itertools</span>(<span class="pl-smi">val</span>):
  713. c1s, c2s <span class="pl-k">=</span> itertools.tee(val)
  714. <span class="pl-c1">next</span>(c2s, <span class="pl-c1">None</span>)
  715. total <span class="pl-k">=</span> <span class="pl-c1">0</span>
  716. <span class="pl-k">for</span> c1, c2 <span class="pl-k">in</span> <span class="pl-c1">zip</span>(c1s, c2s):
  717. <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2:
  718. total <span class="pl-k">+=</span> <span class="pl-c1">1</span>
  719. <span class="pl-k">return</span> total</pre>
  720. </div>
  721. <h3>Why not C/C++/Nim/Go/Ĺua/PyPy/{other language}?</h3>
  722. <p>Ok, that is not the purpose of this post, this post was never about comparing <code>Rust</code> X <code>other language</code>, this post was specifically about <strong>how to use Rust to extend and speed up Python</strong> and by doing that it means you have a good reason to choose Rust instead of <code>other language</code> or by its ecosystem or by its safety and tooling or just to follow the hype, or simply because you like Rust doesn&#8217;t matter the reason, this post is here to show how to use it with <strong>Python</strong>.</p>
  723. <p>I (personally) may say that Rust is more <code>future proof</code> as it is new and there are lots of improvements to come, also because of its ecosystem, tooling, and community and also because I feel comfortable with Rust syntax, I really like it!</p>
  724. <p>So, as expected people started complaining about the use of other languages and it becomes a sort of benchmark, and I think it is cool!</p>
  725. <p>So as part of my request for improvements some people on <a href="https://news.ycombinator.com/item?id=15719254" rel="nofollow">Hacker News</a> also sent ideas, <a href="https://github.com/martinxyz">martinxyz</a> sent an implementation using C and SWIG that performed very well.</p>
  726. <p>C Code (swig boilerplate omitted)</p>
  727. <div class="highlight highlight-source-c">
  728. <pre><span class="pl-c1">uint64_t</span> <span class="pl-en">count_byte_doubles</span>(<span class="pl-k">char</span> * str) {
  729. <span class="pl-c1">uint64_t</span> count = <span class="pl-c1">0</span>;
  730. <span class="pl-k">while</span> (str[<span class="pl-c1">0</span>] &amp;&amp; str[<span class="pl-c1">1</span>]) {
  731. <span class="pl-k">if</span> (str[<span class="pl-c1">0</span>] == str[<span class="pl-c1">1</span>]) count++;
  732. str++;
  733. }
  734. <span class="pl-k">return</span> count;
  735. }</pre>
  736. </div>
  737. <p>And our fellow Red Hatter <a href="https://github.com/cuviper">Josh Stone</a> improved the Rust implementation again by replacing <code>chars</code> with <code>bytes</code> so it is a fair competition with <code>C</code> as C is comparing bytes instead of Unicode chars.</p>
  738. <div class="highlight highlight-source-rust">
  739. <pre><span class="pl-k">fn</span> <span class="pl-en">count_doubles_once_bytes</span>(_py: Python, val: <span class="pl-k">&amp;</span><span class="pl-k">str</span>) -&gt; PyResult&lt;<span class="pl-k">u64</span>&gt; {
  740. <span class="pl-k">let</span> <span class="pl-k">mut</span> total <span class="pl-k">=</span> <span class="pl-c1">0u64</span>;
  741. <span class="pl-k">let</span> <span class="pl-k">mut</span> chars <span class="pl-k">=</span> val.<span class="pl-en">bytes</span>();
  742. <span class="pl-k">if</span> <span class="pl-k">let</span> <span class="pl-c1">Some</span>(<span class="pl-k">mut</span> c1) <span class="pl-k">=</span> chars.<span class="pl-en">next</span>() {
  743. <span class="pl-k">for</span> c2 <span class="pl-k">in</span> chars {
  744. <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2 {
  745. total <span class="pl-k">+=</span> <span class="pl-c1">1</span>;
  746. }
  747. c1 <span class="pl-k">=</span> c2;
  748. }
  749. }
  750. <span class="pl-c1">Ok</span>(total)
  751. }</pre>
  752. </div>
  753. <p>There are also ideas to compare Python <code>list comprehension</code> and <code>numpy</code> so I included here</p>
  754. <p>Numpy:</p>
  755. <div class="highlight highlight-source-python">
  756. <pre><span class="pl-k">import</span> numpy <span class="pl-k">as</span> np
  757. <span class="pl-k">def</span> <span class="pl-en">count_double_numpy</span>(<span class="pl-smi">val</span>):
  758. ng<span class="pl-k">=</span>np.fromstring(val,<span class="pl-v">dtype</span><span class="pl-k">=</span>np.byte)
  759. <span class="pl-k">return</span> np.sum(ng[:<span class="pl-k">-</span><span class="pl-c1">1</span>]<span class="pl-k">==</span>ng[<span class="pl-c1">1</span>:])</pre>
  760. </div>
  761. <p>List comprehension</p>
  762. <div class="highlight highlight-source-python">
  763. <pre><span class="pl-k">def</span> <span class="pl-en">count_doubles_comprehension</span>(<span class="pl-smi">val</span>):
  764. <span class="pl-k">return</span> <span class="pl-c1">sum</span>(<span class="pl-c1">1</span> <span class="pl-k">for</span> c1, c2 <span class="pl-k">in</span> <span class="pl-c1">zip</span>(val, val[<span class="pl-c1">1</span>:]) <span class="pl-k">if</span> c1 <span class="pl-k">==</span> c2)</pre>
  765. </div>
  766. <p>The complete test case is on repository <code>test_all.py</code> file.</p>
  767. <h2><a id="user-content-new-results" class="anchor" href="https://github.com/rochacbruno/rust-python-example#new-results"></a>New Results</h2>
  768. <p><strong>NOTE</strong>: Have in mind that the comparison was done in the same environment and may have some differences if run in a different environment using another compiler and/or different tags.</p>
  769. <div class="highlight highlight-source-shell">
  770. <pre>-------------------------------------------------------------------------------------------------
  771. Name (time <span class="pl-k">in</span> us) Min Max Mean
  772. -------------------------------------------------------------------------------------------------
  773. <strong>test_rust_bytes_once</strong> 476.7920 (1.0) 830.5610 (1.0) <strong>486.6116 (1.0)</strong>
  774. <strong>test_c_swig_bytes_once</strong> 795.3460 (1.67) 1,504.3380 (1.81) <strong>827.3898 (1.70)</strong>
  775. test_rust_once 985.9520 (2.07) 1,483.8120 (1.79) 1,017.4251 (2.09)
  776. <strong>test_numpy</strong> 1,001.3880 (2.10) 2,461.1200 (2.96) <strong>1,274.8132 (2.62)</strong>
  777. test_rust 2,555.0810 (5.36) 3,066.0430 (3.69) 2,609.7403 (5.36)
  778. test_regex 24,787.0670 (51.99) 26,513.1520 (31.92) 25,333.8143 (52.06)
  779. test_pure_python_once 36,447.0790 (76.44) 48,596.5340 (58.51) 38,074.5863 (78.24)
  780. <strong>test_python_comprehension</strong> 49,166.0560 (103.12) 50,832.1220 (61.20) <strong>49,699.2122 (102.13)</strong>
  781. test_pure_python 49,586.3750 (104.00) 50,697.3780 (61.04) 50,148.6596 (103.06)
  782. test_itertools 56,762.8920 (119.05) 69,660.0200 (83.87) 58,402.9442 (120.02)
  783. -------------------------------------------------------------------------------------------------
  784. </pre>
  785. </div>
  786. <ul>
  787. <li>The <code>new Rust implementation comparing bytes</code> is <strong>2x better</strong> than the old comparing Unicode <code>chars</code></li>
  788. <li>The <code>Rust</code> version is still better than the <code>C</code> using SWIG</li>
  789. <li><code>Rust</code> comparing <code>unicode chars</code> is still better than <code>numpy</code></li>
  790. <li>However <code>Numpy</code> is better than the <code>first Rust implementation</code> which had the problem of <strong>double iteration over the unicode chars</strong></li>
  791. <li>Using a <code>list comprehension</code> does not make significative difference than using <code>pure Python</code></li>
  792. </ul>
  793. <blockquote><p>NOTE: If you want to propose changes or improvements send a PR here: <a href="https://github.com/rochacbruno/rust-python-example/">https://github.com/rochacbruno/rust-python-example/</a></p></blockquote>
  794. <h1><a id="user-content-conclusion" class="anchor" href="https://github.com/rochacbruno/rust-python-example#conclusion"></a>Conclusion</h1>
  795. <p>Back to the purpose of this post &#8220;How to Speed Up your Python with Rust&#8221; we started with:</p>
  796. <p>&#8211; <strong>Pure Python</strong> function taking <strong>102 ms.<br />
  797. </strong>&#8211; Improved with <strong>Numpy </strong>(which is implemented in C) to take <strong>3 ms.<br />
  798. </strong>&#8211; Ended with <strong>Rust</strong> taking <strong>1 ms.</strong></p>
  799. <p>In this example <strong>Rust</strong> performed <strong>100x faster</strong> than our <strong>Pure </strong>Python.</p>
  800. <p><code>Rust</code> will not magically save you, you must know the language to be able to implement the clever solution and once implemented in the right it worth as much as C in terms of performance and also comes with amazing tooling, ecosystem, community and safety bonuses.</p>
  801. <p><code>Rust</code> may not be <strong>yet</strong> the <code>general purpose language</code> of choice by its level of complexity and may not be the better choice yet to write common simple <code>applications</code> such as <code>web</code> sites and <code>test automation</code> scripts.</p>
  802. <p>However, for <code>specific parts</code> of the project where Python is known to be the bottleneck and your natural choice would be implementing a <code>C/C++</code> extension, writing this extension in Rust seems easy and better to maintain.</p>
  803. <p>There are still many improvements to come in Rust and lots of others crates to offer <code>Python &lt;--&gt; Rust</code> integration. Even if you are not including the language in your tool belt right now, it is really worth to keep an eye open to the future!</p>
  804. <h2><a id="user-content-credits" class="anchor" href="https://github.com/rochacbruno/rust-python-example#credits"></a>References</h2>
  805. <p>The code snippets for the examples showed here are available in GitHub repo: <a href="https://github.com/rochacbruno/rust-python-example">https://github.com/rochacbruno/rust-python-example</a>.</p>
  806. <p>The examples in this publication are inspired by <code>Extending Python with Rust</code> talk by <strong>Samuel Cormier-Iijima</strong> in <strong>Pycon Canada</strong>. video here: <a href="https://www.youtube.com/watch?v=-ylbuEzkG4M">https://www.youtube.com/watch?v=-ylbuEzkG4M</a>.</p>
  807. <p>Also by <code>My Python is a little Rust-y</code> by <strong>Dan Callahan</strong> in <strong>Pycon Montreal</strong>. video here: <a href="https://www.youtube.com/watch?v=3CwJ0MH-4MA">https://www.youtube.com/watch?v=3CwJ0MH-4MA</a>.</p>
  808. <p>Other references:</p>
  809. <ul>
  810. <li><a href="https://github.com/mitsuhiko/snaek">https://github.com/mitsuhiko/snaek</a></li>
  811. <li><a href="https://github.com/PyO3/pyo3">https://github.com/PyO3/pyo3</a></li>
  812. <li><a href="https://pypi.python.org/pypi/setuptools-rust">https://pypi.python.org/pypi/setuptools-rust</a></li>
  813. <li><a href="https://github.com/mckaymatt/cookiecutter-pypackage-rust-cross-platform-publish">https://github.com/mckaymatt/cookiecutter-pypackage-rust-cross-platform-publish</a></li>
  814. <li><a href="http://jakegoulding.com/rust-ffi-omnibus/">http://jakegoulding.com/rust-ffi-omnibus/</a></li>
  815. <li><a href="https://github.com/urschrei/polylabel-rs/blob/master/src/ffi.rs">https://github.com/urschrei/polylabel-rs/blob/master/src/ffi.rs</a></li>
  816. <li><a href="https://bheisler.github.io/post/calling-rust-in-python/">https://bheisler.github.io/post/calling-rust-in-python/</a></li>
  817. <li><a href="https://github.com/saethlin/rust-lather">https://github.com/saethlin/rust-lather</a></li>
  818. </ul>
  819. <p><strong>Join Community:</strong></p>
  820. <p>Join Rust community, you can find group links in <a href="https://www.rust-lang.org/en-US/community.html">https://www.rust-lang.org/en-US/community.html</a>.</p>
  821. <p><strong>If you speak Portuguese,</strong> I recommend you to join <a href="https://t.me/rustlangbr">https://t.me/rustlangbr</a> and there is the <a href="http://bit.ly/canalrustbr">http://bit.ly/canalrustbr</a> on Youtube.</p>
  822. <h2><a id="user-content-author" class="anchor" href="https://github.com/rochacbruno/rust-python-example#author"></a>Author</h2>
  823. <p><strong>Bruno Rocha</strong></p>
  824. <ul>
  825. <li>Senior Quality Engineer at <strong>Red Hat</strong></li>
  826. <li>Teaching Python and Flask at <a href="http://CursoDePython.com.br">CursoDePython.com.br</a></li>
  827. <li>Fellow Member of Python Software Foundation</li>
  828. <li>Member of RustBR study group</li>
  829. </ul>
  830. <p>More info: <a href="http://about.me/rochacbruno">http://about.me/rochacbruno</a> and <a href="http://brunorocha.org/">http://brunorocha.org</a></p>
  831. <p><hr />
  832. <p><b>Whether you are new to Containers or have experience, downloading this </b><a href="https://developers.redhat.com/promotions/docker-cheatsheet/"><b>cheat sheet</b></a><b> can assist you when encountering tasks you haven’t done lately. </b></p></p>
  833. </article>
  834. </section>
  835. <nav id="jumpto">
  836. <p>
  837. <a href="/david/blog/">Accueil du blog</a> |
  838. <a href="https://developers.redhat.com/blog/2017/11/16/speed-python-using-rust/">Source originale</a> |
  839. <a href="/david/stream/2019/">Accueil du flux</a>
  840. </p>
  841. </nav>
  842. <footer>
  843. <div>
  844. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  845. <p>
  846. Bonjour/Hi!
  847. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  848. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  849. </p>
  850. <p>
  851. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  852. </p>
  853. <p>
  854. Voici quelques articles choisis :
  855. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  856. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  857. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  858. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  859. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  860. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  861. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  862. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  863. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  864. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  865. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  866. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  867. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  868. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  869. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  870. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  871. </p>
  872. <p>
  873. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  874. </p>
  875. <p>
  876. Je ne traque pas ta navigation mais mon
  877. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  878. conserve des logs d’accès.
  879. </p>
  880. </div>
  881. </footer>
  882. <script type="text/javascript">
  883. ;(_ => {
  884. const jumper = document.getElementById('jumper')
  885. jumper.addEventListener('click', e => {
  886. e.preventDefault()
  887. const anchor = e.target.getAttribute('href')
  888. const targetEl = document.getElementById(anchor.substring(1))
  889. targetEl.scrollIntoView({behavior: 'smooth'})
  890. })
  891. })()
  892. </script>