<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[BlackRock Engineering - Medium]]></title>
        <description><![CDATA[Learn how we’re solving complex engineering problems at BlackRock. - Medium]]></description>
        <link>https://engineering.blackrock.com?source=rss----e8ae0174b0d8---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>BlackRock Engineering - Medium</title>
            <link>https://engineering.blackrock.com?source=rss----e8ae0174b0d8---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Tue, 07 Apr 2026 16:36:52 GMT</lastBuildDate>
        <atom:link href="https://engineering.blackrock.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[From Survival to Leadership: My BlackRock TLx Story]]></title>
            <link>https://engineering.blackrock.com/from-survival-to-leadership-my-blackrock-tlx-story-2f921e93a282?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/2f921e93a282</guid>
            <category><![CDATA[career-advice]]></category>
            <category><![CDATA[career-development]]></category>
            <category><![CDATA[growth]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[talent]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Tue, 20 May 2025 17:46:27 GMT</pubDate>
            <atom:updated>2025-04-09T21:39:16.265Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/639/1*gZ5QYoo-nwfLywi8IU8a3g.jpeg" /></figure><h3>From survival to leadership: My BlackRock TLx story</h3><p>BlackRock Product Manager, Jamal Smith, shares his personal career journey and growth as a leader — through the talent program “TLx”</p><blockquote><em>By: </em><strong><em>Jamal Smith</em></strong><em>, Senior Product Manager for Aladdin UX at BlackRock</em></blockquote><p>There’s a moment in every career when you realize you’re standing at a crossroads. For me, that moment came during the height of COVID, when it felt like everyone in tech was questioning their path. Colleagues were departing for lucrative opportunities elsewhere, FOMO was running high, and the “Great Resignation” was in full swing.</p><p>Like many others, I found myself wondering: should I follow the well-worn path to another firm, or was there something deeper worth building where I was?</p><p>But to appreciate that decision, you first need to understand how I got there. You see, my journey to BlackRock was anything but traditional. After law school and at the height of another “GR” — this time, the “Great Recession” — I unexpectedly found myself jobless, homeless, and living out of my car to get by. I had just moved to Miami, excited for a new career and chapter, but was laid off within a few short months and joined the millions of displaced workers facing mounting bills and no prospects. Though I eventually got back on my feet, finding a salaried position after a long stretch of hourly jobs, my confidence in myself seemed irrevocably shaken.</p><p>The path from there to a career in finance wasn’t just unlikely — it felt impossible. When I first applied to BlackRock on the recommendation of a friend, I remember viewing it as some interesting interview practice, nothing more. I didn’t know anything about finance beyond that I was sure I wasn’t worthy to be here. I remember walking into the office for my final interview, wearing a suit I’d bought the night before, feeling like an impostor in the heart of Wall Street. But BlackRock surprised me. Despite my unconventional background, despite my self-doubt, they saw something in me that even I couldn’t see in the mirror. They didn’t just give me a job — they gave me a chance to build my life anew. And they kept investing in me.</p><p>About a year in, I remember being surprised at receiving a particularly significant compensation boost. My manager looked at me and said something I’ll never forget: “We pay for performance here — never underestimate your worth.” She saw my hesitation and took the time to have a deeper conversation about value and self-advocacy. She taught me to understand my contributions, especially as a person from marginalized groups, and to never be afraid to ask for what I wanted. “You’ll only ever get three answers,” she said, “yes, no, or not right now — and then the power is in your hands.</p><p>Fast forward to 2020 and I found myself facing that critical decision. Was it time for me to explore new opportunities, like many friends across my industry were doing? But something held me back — not fear, but instead a growing realization that I needed clarity before I could make a meaningful choice about my own path ahead.</p><p>That’s what led me to TLx.</p><h4>The TLx Difference</h4><p>BlackRock has always invested heavily in developing its people — from rich online academies to mentorship programs to leadership training. But I was drawn to our Tech Leadership Accelerator (‘TLx’), an intensive and interactive six-month talent program, for employees in technology roles, designed to provide a comprehensive learning and networking experience. TLx is an application-only program and has been oversubscribed for all 7 cohorts since launching. I sensed TLx might help me answer the deeper questions I was wrestling with: What kind of leader did I want to become? What does tech leadership even mean, particularly in a place like BlackRock? What skills did I have to develop and harness?</p><p>Instead of all the answers, what I discovered through TLx is that there is no single path to tech leadership — and that’s actually the point. Leading in technology is already a unique challenge — you’re navigating an industry that transforms almost daily, trying to bridge the gap between deep technical expertise and broader leadership skills. But at BlackRock, we’re doing this at the intersection of finance and technology, where the stakes couldn’t be higher, and the complexity multiplies with every new market innovation.</p><p>TLx doesn’t just acknowledge these layered challenges — it embraces them, helping us develop the adaptability and breadth of vision required to lead effectively in this complex intersection. The program combines personal coaching, master classes with industry leaders, and small sessions with BlackRock’s top executives. But what makes TLx unique is how it addresses the fundamental transformation that happens when a technical expert begins to step into leadership. It’s not just about adding management skills to your technical toolkit, much like a sales person becoming a CRO or an engineer becoming a CTO. Your technical skills remain vital, but they’re no longer your exclusive identity.</p><h4><strong>Beyond Technical Skills</strong></h4><p>So what other skills did I learn? A key one was about emotional regulation — something I hadn’t expected to focus on in a tech leadership program. I learned that as a leader, your energy sets the tone for your entire team. If you’re having a bad day, everyone has a bad day. If you’re excited about a project, that enthusiasm ripples through the organization. Managing this dynamic isn’t just a soft skill — it’s a crucial leadership competency.</p><p>I also learned that leadership is about crafting and telling compelling stories. Whether you’re seeking resources, driving change, or building consensus, your ability to communicate a vision matters as much as your technical capabilities. At BlackRock, where we’re constantly pushing the boundaries of what’s possible in fintech, this ability to translate technical complexity into compelling narratives is essential.</p><h4>The Power of Perspective</h4><p>But perhaps the most transformative aspect of TLx was how it helped me reframe my own story. During one of our sessions with an external speaker — this time an unlikely CEO who had also experienced homelessness — I had a revelation. She shared how she had reframed her past struggles not as limitations, but as proof of her resilience. “You’ve survived the worst,” she said. “What do you have to fear?… Why not take the risk? Why not raise your hand? Why not try?”</p><p>That perspective shift changed everything. I stopped seeing my past as a liability and started recognizing it as a source of strength. It gave me the courage to take risks, to vocalize my interest in new opportunities, to step fully into leadership.</p><h4>A Community of Leaders</h4><p>The power of TLx extends beyond individual transformations. The program brings together people from across BlackRock who might never otherwise connect: veterans who’ve been with the firm for years, newcomers bringing fresh perspectives from other industries, colleagues from different regions of the world. This diversity of experience and viewpoint is intentional, and it creates something special.</p><p>I met colleagues I didn’t even know were in my department until we met in TLx. Today, I regularly schedule one-on-ones and check-ins with people I met through the program who work in entirely different parts of the business. These connections have opened my eyes to how similar our challenges are, despite our different roles. As someone in a unique position — not quite a traditional engineer nor product manager — I had often felt somewhat isolated. TLx showed me that many of the leadership challenges I face are universal, even if our day-to-day work differs.</p><p>These relationships have broken down silos, sparked collaborations, and created a network of support that extends far beyond the six-month program. When you’re trying to navigate a career in tech leadership — especially in finance — having a community of peers who understand your challenges is invaluable.</p><h3>The Choice to Lead</h3><p>Ultimately, the most important lesson I learned through this journey is that your career is your choice — and it’s one you get to make every single day. BlackRock and TLx helped me understand that being the head of a massive organization isn’t the only path to leadership; you can choose to be a senior IC, a line manager, a CTO or anything in between and every one of those is equally valid. What matters is making it a conscious choice, fueling your direction with intention, and understanding the why behind your path — even as it twists and turns.</p><p>Today, I’m grateful I chose to stay and grow at BlackRock. That moment of COVID-induced FOMO led me to something far more valuable than a short-term career move — it led me to clarity about who I am as a leader and where I want to go. Far beyond a sharper set of skills, I’ve found my voice and my purpose.</p><p>Learn more about <a href="https://careers.blackrock.com/life-at-blackrock-2/technology/">technology careers at BlackRock</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2f921e93a282" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/from-survival-to-leadership-my-blackrock-tlx-story-2f921e93a282">From Survival to Leadership: My BlackRock TLx Story</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[AI is making engineering more human, not less]]></title>
            <link>https://engineering.blackrock.com/ai-is-making-engineering-more-human-not-less-66f3bfc56771?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/66f3bfc56771</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[copilot-programming]]></category>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[artificial-intelligence]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Mon, 21 Oct 2024 20:10:55 GMT</pubDate>
            <atom:updated>2024-10-21T20:10:55.357Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bwru7m-C1QSzX6J58ppSZw.jpeg" /></figure><blockquote><em>By: </em><strong><em>Lance Braunstein</em></strong><em>, Head of Aladdin Engineering at BlackRock</em></blockquote><p>When people talk about AI and jobs, the conversation often veers towards a dystopian future where robots have taken all our work. Putting aside my de facto disclaimer as a technologist that artificial intelligence has been around for decades (and has been changing jobs all the while), it’s an understandable fear, especially in technical fields like software engineering. After all, if AI can write code, detect and debug errors, automate testing, and facilitate DevOps, what’s left for humans?</p><p>As the Head of Aladdin Engineering at BlackRock, I’m not just theorizing about AI’s impact on jobs — I’m living it. And what I’m witnessing isn’t job displacement, but a profound transformation of the engineering landscape.</p><p>Let’s start with a counterintuitive truth: AI is making engineering more human, not less.</p><p>While AI excels at generating the first draft of code, the essence of software engineering lies in problem-solving, collaboration, applying context, and seeding innovation. These deeply human skills are becoming even more crucial in our current era, as engineers tackle increasingly complex challenges that machines alone can’t solve. While we race to adapt to AI, we’re simultaneously confronting blockchain, cloud computing, and a host of other globally disruptive technologies evolving beneath our feet. Their application and integration require novelty and nuance much more than efficiency.</p><p>Artificial intelligence has been reshaping the engineering landscape for years, creating demand for new roles and skills, but perhaps never at the current pace. The call for machine learning engineers, big data specialists, database and network scientists, and statisticians is surging across the industry. The burden on our platforms for resiliency, scale, security, and performance is multiplying as our universe of prompt engineers expands, creating a vacuum for talented engineers. And the field of AI ethics has emerged as critical, requiring a multidisciplinary approach that blends technical expertise with knowledge of ethics, law, and social science.</p><p>Even conventional engineering roles are transforming. It’s been years since I, individually, pushed a code change but when we first started experimenting with developer copilots, I called server engineers on the floor into my office to see what I’d produced (and tell me how to make it better). Our software developers have become orchestrators of complex ecosystems, harmonizing human-written and AI-generated code on a platform with thirty years of proud history.</p><p>This new paradigm demands not just technical proficiency, but also heightened creativity, sound judgment, and a comprehensive grasp of the problem domain. And every traditional engineering role — from security specialists to application engineers to DevOps — is being reframed with an expectation for understanding how to leverage and adapt to AI pipelines, models, tools, and risk. To say it’s changing the way we hire and how we think about composing teams is an understatement. The bar for talent at BlackRock has always been high but more than ever it’s about left brain, right brain synergy and finding innovative collaborators.</p><p>The ripple effects of this AI revolution extend far beyond our engineering teams, touching every corner of BlackRock. Take our recent <a href="https://www.microsoft.com/en-us/industry/blog/financial-services/2024/09/30/elevating-investment-management-tech-ai-powered-leadership-from-blackrock-and-microsoft/?hss_meta=eyJvcmdhbml6YXRpb25faWQiOiAxODkyLCAiZ3JvdXBfaWQiOiAxMTIyOTMyLCAiYXNzZXRfaWQiOiAyMjY1NzAyLCAiZ3JvdXBfY29udGVudF9pZCI6IDEzNjkzOTc2OSwgImdyb3VwX25ldHdvcmtfY29udGVudF9pZCI6IDIwODgxMzY5M30%3D">launch of Aladdin Copilot,</a> for instance, which serves as a connective tissue for our clients to generate insights across our investment platform. It doesn’t replace our client service representatives; instead, the Copilot amplifies their capabilities, enabling them to spend more time understanding and engaging with customer needs and less time executing rote queries.</p><p>This pattern of AI-driven enhancement repeats across our organization. While we’ve leveraged AI and machine learning in our investment process for nearly two decades, recent advancement in large language models represent a step-change in our capabilities. In our investment teams, we’re harnessing fine-tuned LLMs to analyze earnings call transcripts and forecast market reactions with unprecedented accuracy. As we build equity baskets in response to dynamic, market-relevant themes, <a href="https://www.blackrock.com/us/individual/insights/ai-investing">we’re blending proprietary data with the vast knowledge base of LLMs</a> to create a faster and more flexible process than our portfolio managers could execute alone. And in contract management, AI is converting complex legal terms into codified rules for portfolio management, elevating our legal and operations teams to focus on higher-value areas.</p><p>Across these diverse applications, a common thread emerges: AI is both handling the routine and augmenting the experts, enabling our human talent to explore new frontiers, craft smarter insights and tackle ever evolving questions.</p><p>Of course, this transformation comes with challenges. The pace of change is breathtaking, and skills can become outdated almost overnight. That’s why we continue to make significant investments in ongoing learning and upskilling. BlackRock has emphasized the notion of the citizen developer for years and we recently mandated prompt engineering and AI training for all employees — not just our tech teams. We’ve deployed internal tools like <a href="https://news.microsoft.com/source/features/digital-transformation/how-blackrocks-flight-crew-helped-copilot-for-microsoft-365-take-off/">Copilot for Microsoft 365</a> across our main work systems — word processors, email, intranet, and more — and are actively seeing their adoption grow. In a sense, we’re all becoming engineers now, learning to harness AI to enhance our work.</p><p>As AI continues to advance, it uncovers new possibilities, poses fresh questions, and presents novel challenges. Each of these developments calls for human ingenuity. Our ambitions in technology always outpace our current capabilities. We have an insatiable appetite for innovation and progress, constantly pushing the boundaries of what’s possible.</p><p>This ever-expanding frontier of technological potential is precisely why AI isn’t reducing our need for talent — quite the opposite. It’s enabling us to tackle bigger challenges, to create more value, and to have a greater impact than ever before. We’re not shedding talent — we’re empowering our people to do more, faster, better, and farther than we ever thought possible.</p><p>Check out BlackRock’s open AI engineering roles <a href="https://blackrock.wd1.myworkdayjobs.com/BlackRock_Professional/job/New-York-NY/Applied-AI-Engineer--Associate---VP_R245572">here</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=66f3bfc56771" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/ai-is-making-engineering-more-human-not-less-66f3bfc56771">AI is making engineering more human, not less</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solving the Mystery of the Slow Hash Table]]></title>
            <link>https://engineering.blackrock.com/solving-the-mystery-of-the-slow-hash-table-616627a270c5?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/616627a270c5</guid>
            <category><![CDATA[java]]></category>
            <category><![CDATA[hashmap]]></category>
            <category><![CDATA[apache]]></category>
            <category><![CDATA[hash-table]]></category>
            <category><![CDATA[spark]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Mon, 29 Jul 2024 18:53:12 GMT</pubDate>
            <atom:updated>2024-07-29T18:53:12.735Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*e3Xzs2Zwh6krbQY4hUOLtQ.jpeg" /></figure><h4>The story of a Java hash table gone wrong and how it was fixed</h4><blockquote><strong><em>By: Spenson Shih</em></strong><em>, Principal Engineer, Aladdin Wealth Tech &amp; </em><strong><em>Akash Gupta</em></strong><em>, Senior Engineer II, Aladdin Wealth Tech</em></blockquote><p>One of the most used data structures across programming languages is the hash table. In Java, the <a href="https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html">HashMap</a> class is a hash table implementation that provides a powerful means to store and retrieve data efficiently. There are also many variations of the hash table implementation both built into the JDK or as a 3rd party extension.</p><p>All these various hash tables are built upon the same basic concept. Understanding its inner workings is crucial for any developer seeking to optimize data handling. In this blog, we’ll share with you a case-study of a hash table gone wrong, and how we identified and fixed the problem.</p><h3>Very Large Portfolios</h3><p>In the Aladdin Wealth business at BlackRock, we have many kinds of clients and are always evolving and enhancing the platform to support different use cases, especially where large scale is needed. Scale usually comes in the form of many portfolios, sometimes in the millions, but in one case, we wanted to use our platform to analyze a client’s entire book of business as if it were a single portfolio. The largest one of these pseudo-portfolios was composed of nearly 50 million positions from around 2 million portfolios, and over 250,000 unique securities.</p><p>We use Apache Spark to distribute workload across nodes for portfolio construction and portfolio analytics computation. This Spark job was expected to take around 4 hours to complete. However, we found that the runtime was actually taking over 16 hours. This was a significant increase and would impact our ability to deliver results for the client in a timely manner.</p><p>Troubleshooting this issue in a massive Spark application is like searching for a needle in a haystack. Our team performed all of the usual troubleshooting steps — we analyzed the GC logs, resource utilization in Grafana, enabled debug logs, and so on.</p><p>It took a while, but using the data collected from the above, we extracted the execution times of each analytics calculator for various portfolio sizes. The data table below summarizes the execution times in milliseconds of each calculator for various input sizes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/597/1*mNu-WGy02H6ZroUyENVjRQ.png" /></figure><p>As you can see, the slowness seems to come from the calculation of the Breakdowns. In fact, if you plot the portfolio size vs the time in millis, you will notice that the Breakdowns Calculator seems to have a quadratic time complexity or O(N2) — as the input size increases, the runtime also increases in a quadratic fashion.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/423/1*wfBu5e_eizatyEKrUxxc0Q.png" /></figure><h3>JMH Benchmark</h3><p>We used the Java Microbenchmarking Harness to attempt to reproduce the slowness when invoking Breakdowns Calculator with the same input size of 250,000 positions. Unfortunately, our attempts to replicate the slow execution times using a benchmark were unsuccessful. The benchmark result shows that the calculator completes in approximately 20 seconds for the same input size, compared to over 4 hours in the Spark job.</p><h3>Position Contributors</h3><p>Part of the Breakdowns Calculator involves calculating the contribution of each position. We decided to disable the calculation of position contributions to further isolate where the bottleneck is coming from. To also speed up our test runs, we limited the runs to only the portfolio of 250,000 positions, as a single re-run took 16 hours to finish. By disabling the position contributors, the Breakdowns Calculator for the portfolio of 250k position completed in under 20 seconds. We had isolated the problem to somewhere in the position contributor’s code.</p><p>Let’s focus on the part that calculates the breakdown position contributors. We use a library called <a href="https://fastutil.di.unimi.it/">fastutil</a>, an extension to the Java Collections Framework, which provides Collections implementations that avoid the <a href="https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html">autoboxing and unboxing</a> of primitive to object wrappers in Java that can hurt performance as seen <a href="https://dzone.com/articles/java-performance-notes-autoboxing-unboxing">here</a>. The primitive double map is used to accumulate the sum of the position contributions keyed with the unique position identifier and the security identifier:</p><pre>if (includeContributors) {<br>contributors.computeDouble(Pair.of(position.getId(), position.getCusip()), (k, v) -&gt; defaultIfNull(v, 0.0) + posContribution);<br>}</pre><p>It’s a mystery why this code runs significantly slower compared to our tests using the exact same code and similar sized inputs. The logic is straight-forward, as it simply calculates the sum of the contribution grouped on the position ID. Our benchmarking of fastutil map also shows we can easily build similar sized maps within seconds. Fastutil maps are also used in Risk Calculator — which does not seem to have the same issue as you can see from the timings earlier.</p><p>The advantage of a hash table is that the time complexity to insert and retrieve a value is a constant time O(1) on average. So how can the use of a hash table result in an O(N2) quadratic time complexity when used in our code?</p><h3>Hash Table Basics</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/756/1*w5xJO_z0CosD2dOXNHEMPg.png" /><figcaption>Illustration of a classical hash map using separate chaining. Keys (left) are put into corresponding buckets (middle) obtained by the hash function h. A linked list (right) is constructed per bucket to store key value pairs within the same bucket but with unequal keys. <a href="https://www.researchgate.net/figure/Illustration-of-a-classical-hash-map-using-separate-chaining-Keys-left-are-put-into_fig1_355070749">source</a></figcaption></figure><p>To answer that question, we need to have a basic understanding of how hash tables work:</p><p>· the underlying basic structure is an array of linked lists, representing a bucket</p><p>· when performing a lookup or insertion, calculate the hash of the key</p><p>· <em>mod</em> the hash to give an integer between 0 and the size of the array of buckets — this is the index of the selected bucket</p><p>· iterate through the linked list in the selected bucket, comparing equality of keys</p><p>· if keys match, return the associated value with that key</p><p>All the above operations: calculating the hash, mod, array indexing, and adding element to the end of a linked list, have constant time complexity or O(1) — which is to say as the hash table grows, the time taken to fetch or store a value does not change. This is assuming you have a good hash function, and your elements are evenly distributed across the buckets of your hash table.</p><h3>Hash Collision</h3><p>A hash table lookup operation can be severely degraded if you have a weak hash function. This will result in a lot of hash collisions — when two or more pieces of data in a hash table share the same hash value. In a basic hash table implementation, a bucket usually consists of a linked list. In the event of a collision, the entry will either replace an element in the list or add it to the list. In the worst-case scenario, all entries end up in the same bucket and you end up with a time complexity of O(N) for a lookup or put operation. In Java 8 and above, the HashMap implementation will replace the linked list with a balanced binary tree if a certain threshold is exceeded. This provides a better performance in the case of collision, as lookup and put will become O(log N) instead of O(N). However, better than solving hash collisions is avoiding them from happening in the first place.</p><p>With this in mind, we now focused our attention on the key of the map, where we use Apache Commons Lang3 <a href="https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/tuple/Pair.html">Pair</a> tuple. This is an ImmutablePair class which is a pair of two values — the position identifier and the security identifier. Below is the hash code implementation of the ImmutablePair class:</p><pre>public int hashCode() {<br>return Objects.hashCode(this.getKey()) ^ Objects.hashCode(this.getValue());<br>}</pre><p>Both the position identifier and security identifier are instances of Java String and will have a good hash function. <em>The hash code of our key is effectively the hash code of the position identifier combined with the hash code of the security identifier using a bitwise exclusive or (XOR) operator:</em></p><pre>hashCode(position.getId()) XOR hashCode(position.getCusip()</pre><p><em>XOR</em> is a bitwise operator where if input bits are the same, then the output is 0, else 1.</p><p>During the ETL phase of the Spark job, we collapsed positions with the same securities into a single item when building the portfolios. Let’s use the below example 3 accounts:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kgGRcVG0N0sbbrFEB7FmmA.png" /></figure><p>The pseudo-portfolio will be a combination of all 3 accounts, with 9 positions. But by collapsing the same positions into the combined entry, we reduce the number of positions to just the unique tickers, and we end up with a portfolio with 6 entries.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/409/1*ADsgQHk6_4kzO21rhP_ZjQ.png" /></figure><p>In real world data, this process reduced the number of entries in the largest portfolio from 50 million entries to just 250,000. To identify the contributors of each security, the position identifier was assigned to be the same as the security identifier.</p><p>The eureka moment came as soon as we combined this information with the hash code implementation of the Apache Commons Lang3 Pair class. Everything made perfect sense. With the position identifier and security identifier having the same value for all 250,000 entries, all the hash code is calculated as 0, and we end up with a hash collision for every single entry.</p><pre>hashCode(&quot;IVV&quot;) XOR hashCode(&quot;IVV&quot;) = 72905 XOR 72905 = 0<br>hashCode(&quot;AAPL&quot;) XOR hashCode(&quot;AAPL&quot;) = 2001436 XOR 2001436 = 0<br>hashCode(&quot;AGG&quot;) XOR hashCode(&quot;AGG&quot;) = 64737 XOR 64737 = 0<br>…</pre><p>The hash collision means every single entry in the map ends up in the same bucket. The computeDouble function becomes O(N) complexity executed within an O(N) complexity of the breakdowns calculator looping through the positions — effectively giving us O(N2) time complexity that we see in our production runs.</p><p>This explains why our tests and benchmarks were unable to reproduce the slowness and we never suspected a hash collision, as the position identifier and security identifier are never the same values. The same code running in our interactive servers also did not encounter the same slowness for the same reason. It’s only during the portfolio analysis where the position identifier and security identifier will have the same value and we ended up with the hash collisions.</p><h3>The Fix</h3><p>Knowing the reason behind the slow execution, the fix was simple and straight-forward. We updated the map to use just the position identifier as the key, then store the security identifier together with the accumulated position contributors as part of the value instead, by creating a new MutablePositionBreakdownContributor class.</p><pre>if (includeContributors) {<br>contributors.computeIfAbsent(position.getId(), posId -&gt; new MutablePositionBreakdownContributor(posId, position.getCusip())).addContribution(posContribution);<br>}</pre><p>This change eliminates the hash collision for the portfolios, which means the average time complexity of the map operation is now back to constant time O(1), and our breakdowns calculator now has a time complexity of O(N) for both interactive and batch process.</p><p>The data extracted from logs of the latest run after the patch confirms the breakdowns for the very large portfolio now completes in just under 17 seconds even with position contributors enabled. Overall, this change has brought back the runtime of the Spark job from 16 hours back to the expected 4 hours.</p><p>Looking back at the commit history of how this came to be, it turns out the change to use the HashMap was part of a previous optimization to make the breakdowns calculator faster. That change involved the use of a cached Spring Expression evaluation context, but a small refactor of the contribution logic was made at the same time to reduce intermediate results. At that time, a benchmark was also performed, but as we now know the benchmark failed to simulate the conditions in the batch Spark jobs that lead to the hash collisions.</p><p>Of course, there are many other factors that can affect the performance of a hash table implementation. These are very well-studied problems, and you can find several resources online about this topic.</p><h3>Lessons learned</h3><p>In our point of view, we considered the hash function of Apache Commons Lang3 Pair class to be weak, since having the same value for both elements always result in a hash code of 0. The implementation does not violate the hash code contract, but if used as keys on hash tables it will severely degrade performance if both elements happen to have the same value. We considered reporting this as an issue to the Apache Commons team. However, a quick search shows <a href="https://issues.apache.org/jira/browse/LANG-1464">this</a> has already been identified back in 2019 and is still open. The explanation being this is a weakness in Java’s Map.Entry interface hash code contract and needs to be addressed there. In short, do not use Apache’s Pair class or any class that implements the Map.Entry interface as a key in any hash table or hash-based data structure — like a HashSet in Java. As with any hash-based data structures, make sure you fully understand what’s going on inside your hash functions. As you can see, this could mean a difference of several hours of compute time if you are dealing with very large amounts of data.</p><p>In conclusion, this case study highlights the importance of understanding the inner workings of data structures, even those provided by the programming language or reliable third parties. By identifying the root cause of the hash collision and implementing a simple fix, the team was able to bring the runtime of the analysis job back to its original duration. This experience also led to the Distributed Compute team to develop a mechanism to monitor Spark jobs and gather detailed telemetry, allowing for easier identification and isolation of bottlenecks in production runs.</p><p>Learn more about <a href="https://careers.blackrock.com/life-at-blackrock-2/technology/">technology careers at BlackRock</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=616627a270c5" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/solving-the-mystery-of-the-slow-hash-table-616627a270c5">Solving the Mystery of the Slow Hash Table</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unlocking Ambiguity in Semantic Search: Introducing Blowfish]]></title>
            <link>https://engineering.blackrock.com/unlocking-ambiguity-in-semantic-search-introducing-blowfish-e2a83c5c05a2?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/e2a83c5c05a2</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[statistical-analysis]]></category>
            <category><![CDATA[data-scientist]]></category>
            <category><![CDATA[semantic-search]]></category>
            <category><![CDATA[data]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Fri, 28 Jun 2024 20:26:13 GMT</pubDate>
            <atom:updated>2024-10-29T21:57:07.643Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/614/1*cxADZ3XwuqWKnzIF1ePX4Q.jpeg" /></figure><h4>Aladdin Data Scientists Thomas and Alex recap a recent white paper where they introduce practical insights into topological and statistical signatures for quantifying ambiguity in semantic search</h4><blockquote><em>By: </em><strong><em>Thomas Roland Barillot</em></strong><em>, </em>Lead Data Scientist, Aladdin Engineering,<em> &amp; </em><strong><em>Alex De Castro, PhD,</em></strong><em> </em>Senior Researcher and Data Scientist II, Aladdin Engineering</blockquote><p>In the digital age, where data is abundant and readily available, semantic search has become an essential tool for extracting relevant information. However, the challenge of ambiguity in search results persists, complicating the retrieval process. In our recently published paper, “<a href="https://arxiv.org/pdf/2406.07990"><strong>Blowfish: Topological and Statistical Signatures for Quantifying Ambiguity in Semantic Search</strong></a><strong>”</strong>, we offer novel and practical insights into this issue.</p><h4><strong>Understanding Ambiguity in Semantic Search</strong></h4><p>Semantic search aims to enhance search accuracy by understanding the contextual meaning of terms within the searchable data space. For example, a traditional keyword search for “apple” might return documents about the fruit and the technology company indiscriminately. In contrast, semantic search can differentiate between these contexts based on surrounding content, returning more relevant results. However, ambiguity arises when a search query or the context of documents is unclear or open to multiple interpretations. For instance, a search for “Java” could refer to the programming language, an Indonesian island, or a type of coffee. The paper introduces a method to quantify and analyze this ambiguity using both topological and statistical approaches, providing a clearer understanding of its impact on search accuracy.</p><h4><strong>Blowfish Framework</strong></h4><p>The Blowfish framework leverages advanced techniques from topology and statistics to identify and measure ambiguity in semantic search. The core innovation lies in the analysis of sentence embeddings — mathematical representations of sentences that capture their semantic meaning. By examining the homological features, which are essentially the shapes and structures formed by these embeddings in a multi-dimensional space, the authors were able to detect patterns indicative of ambiguity. For example, clusters of sentence embeddings that are closely packed together may indicate a lack of clarity, as multiple meanings are represented similarly. This method provides a novel way to understand and address the challenges posed by ambiguous search queries.</p><h3><strong>Key Findings</strong></h3><h4><strong>1. Topological Signatures</strong></h4><p>Ambiguous queries tend to produce more complex topological structures in the manifold of sentence embeddings. To clarify, “topological structures” refer to the shapes and connections formed by data points in a high-dimensional space, and a “manifold” is this high-dimensional space itself where the data points (sentence embeddings) reside. The complexity of these structures can be visualized and quantified, providing a clear distinction from less ambiguous queries. By analyzing these patterns, we can better understand and address the challenges posed by ambiguous search queries.</p><h4><strong>2. Statistical Analysis</strong></h4><p>Statistical methods enhance our topological findings by measuring how spread out or clustered the sentence embeddings are. Ambiguous queries often result in a more scattered distribution, showing multiple possible meanings or interpretations. Imagine the semantic space of vector representations as a landscape; near ambiguous queries, this landscape tends to resemble a blowfish, with points spreading out in various directions.</p><h4>3. <strong>Experimental Validation</strong></h4><p>The paper presents extensive experimental results, showing that ambiguous queries, when analyzed through Blowfish, exhibit distinct patterns that can be effectively identified and quantified.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/493/1*AnFQx-LmUewtpsekJwCgMw.jpeg" /></figure><h3><strong>Practical Applications</strong></h3><p>The implications of this research are far-reaching. By incorporating Blowfish into existing semantic search systems, it becomes possible to enhance their accuracy and reliability. For instance, in AI-driven applications such as Retrieval Augmented Generation (RAG) systems, better handling of ambiguity can lead to more precise information retrieval and improved user experiences.</p><h3><strong>Conclusion</strong></h3><p>Blowfish represents a significant advancement in the field of semantic search, offering a novel approach to tackle the enduring problem of ambiguity. By blending topological and statistical analyses, this framework not only identifies but also quantifies ambiguity, paving the way for more effective and accurate search technologies.</p><p>Our findings were driven by commercial applications at BlackRock. While building LLM-powered search systems, we noticed a need to understand why certain queries produced useful results and others did not. Factors characterizing good queries, such as clarity and context, became apparent through experimentation. We realized that ambiguity or insufficient context was a significant driver behind poor results. This led us to develop quantitative methods to detect ambiguity, enabling us to explain and audit our vector search systems effectively.</p><p>For a deeper dive into the methodologies and findings, the full paper is available <a href="https://arxiv.org/pdf/2406.07990">here</a>. This research is poised to influence future developments in semantic search, making it a crucial read for those in the field of AI and data retrieval.</p><p>Blowfish is a Python library that quantifies ambiguity in semantic search using a Gaussian Kernel Density Estimation (KDE) model. KDE estimates the probability distribution of data points — in this case, factors based on sentence embeddings — across a continuous space. Blowfish automates embedding and clustering of document chunks, trains on query-answer pairs, and evaluates ambiguity using topological features. Its strength lies in managing complex semantic search cases, contributing to ranking or explanation tasks in Retrieval Augmented Generation (RAG) systems. With flexible model configurations, it provides a robust framework for handling ambiguity and clarity in responses.</p><p>Learn more about <a href="https://careers.blackrock.com/life-at-blackrock-2/technology/">technology careers at BlackRock</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e2a83c5c05a2" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/unlocking-ambiguity-in-semantic-search-introducing-blowfish-e2a83c5c05a2">Unlocking Ambiguity in Semantic Search: Introducing Blowfish</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Open Sourcing the Aladdin SDK: Empowering Python Developers with a Quantitative Edge]]></title>
            <link>https://engineering.blackrock.com/open-sourcing-the-aladdinsdk-empower-python-developers-with-a-quantitative-edge-7f63376061e6?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/7f63376061e6</guid>
            <category><![CDATA[quant]]></category>
            <category><![CDATA[sdk]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[python]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Tue, 07 May 2024 15:54:21 GMT</pubDate>
            <atom:updated>2024-09-20T12:49:29.536Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*0fP53V1LnBzJc9g04OY8BQ.jpeg" /></figure><blockquote><em>By: </em><strong><em>Vedant Naik</em></strong><em>, Lead Engineer, Aladdin Studio, and </em><strong><em>Eli Kalish</em></strong><em>, Product Manager II, Aladdin Studio</em></blockquote><p>In the rapidly changing landscape of software development, the demand for efficient mechanisms to harness data and execute complex strategies has never been greater. Developers often have diverse sets of skills and varying levels of proficiency, introducing a need for simplicity and standardization around how Aladdin, BlackRock’s investment management software, offerings are consumed. We are pleased to announce <a href="https://github.com/blackrock/aladdinsdk">the Aladdin SDK</a>, a bespoke Software Development Kit written in Python that provides client-side code for API and Aladdin Data Cloud (ADC) usage, and several common utilities under one convenient library.</p><h3>Introducing the Aladdin SDK</h3><p>The Aladdin SDK provides pre-built tools and functionality that simplifies connectivity to Aladdin APIs and the Aladdin Data Cloud, so you can accelerate development on top of Aladdin technology. Whether you’re a professional engineer or a code-proficient business user, the Aladdin SDK provides you with the tools and customization to make Aladdin your own.</p><p>Boost code efficiency by minimizing lower-level setup required for API and Aladdin Data Cloud access, and seamlessly handle authentication, request retries, and error handling. With built-in utilities under one convenient library, unlock a wide range of Aladdin capabilities that can be configured specifically for your needs. Streamline your setup and simplify your workflow through common connections, creating a consistent environment that allows you to build safely and securely. And lastly, leverage standardization to simplify code maintenance and accelerate development, so you can focus on delivering impactful business solutions, faster.</p><p>The Aladdin SDK aims to empower more practitioners and citizen developers — professionals with financial expertise and some programming proficiency — to boost their productivity, streamline their development, and build more efficiently at scale.</p><h3>Simplified Access to Aladdin APIs and the Aladdin Data Cloud</h3><p>Using the Aladdin SDK drastically reduces the amount of technical setup necessary to access Aladdin APIs and Data. Aladdin APIs enable clients to build solutions with access to the breadth and depth of Aladdin services. Aladdin SDK plugins enables service providers and consumers to dynamically update API capabilities. The Aladdin Data Cloud is a managed data store, powered by Snowflake technology, that brings together Aladdin and non-Aladdin data as a time-series on a single analytics platform. Both of these offerings are heavily used within BlackRock by quantitative researchers, software developers and citizen developers.</p><p>The Aladdin SDK is a unifying force, providing consistent patterns for developers to interact with curated Aladdin Graph APIs and ADC datasets. Users can access APIs with built-in functionality for OAuth and Basic Authentication, and query Snowflake data with a single line of code.</p><h3>Configuration Driven Implementation</h3><p>The Aladdin SDK allows users to define and modify the SDK’s behavior through easily understandable configuration files. This powerful feature further simplifies the implementation process, while enhancing the SDK’s adaptability to diverse scenarios. Developers can tweak parameters, select preferred authentication mechanisms, and fine-tune strategies through straightforward configurations, making the Aladdin SDK a truly flexible and customizable tool.</p><p>Under the hood, the Aladdin SDK uses <a href="https://www.dynaconf.com/">dynaconf</a> for configuration management. Users can provide a configuration yaml file by pointing to a filesystem location using the ASDK_USER_CONFIG_FILE environment variable. Users can override any of the configurable settings by exporting an environment variable prefixed by `ASDK_`. For a full list of available configurations refer to the official Aladdin SDK <a href="https://github.com/blackrock/aladdinsdk/blob/main/README.md">README.md</a></p><h4>Run Environment Customizations via Configurations</h4><p>The configuration module allows SDK owners, or Development Environment orchestrators, to provide a customized Aladdin SDK experience for their end users. This can be done by setting hard configurations using environment variable overrides, or providing a Default Configuration file at startup.</p><p>Additional configurations can always be provided by using ASDK_USER_CONFIG_FILE to point to a user created file. This will override any default configurations.</p><p>Configurable settings set via environment variables take priority and override all other configurations.</p><h3>Common Development Utilities</h3><p>Having a standard approach for common tasks ensures everyone is building using the same patterns. To achieve this, the Aladdin SDK abstracts away actions such as authentication, retry, error handling, data transformations, file exports, and more. The user can provide configurations that dictate the behavior of each action, rather than having to worry about implementing these from scratch for different projects. This allows all users to simply instantiate an API or ADC client object and proceed with their development tasks.</p><h3>The Aladdin SDK in Action</h3><h4>Making an API call</h4><p>Let’s take an example of making an API call to see how all the above points come together in the real world. We will do this while comparing and contrasting the Aladdin SDK with the current API usage experience.</p><p>For this example, we want to invoke an endpoint on Order API. Specifically, the ‘orders:filter’ endpoint:</p><pre># import libraries for API Calls / Data Analysis<br>import json,uuid,datetime,requests,pprint,math,csv<br>import pandas as pd<br> <br># set Client Environment (from url), Aladdin Username &amp; Password, and APIKey<br>client_env = &#39;&#39;<br>username = &#39;&#39;<br>pwd = &#39;&#39;<br>APIkey= &#39;&#39;<br> <br>today = datetime.date.today().strftime(&#39;%Y-%m-%d&#39;)<br>         <br>#function to generate unique headers when called<br>def generate_headers():<br>    headers = {<br>        &#39;VND.com.blackrock.API-Key&#39;: APIkey,<br>        &#39;VND.com.blackrock.Origin-Timestamp&#39;: <br>            str(datetime.datetime.utcnow()<br>                .replace(microsecond=0).astimezone().isoformat()),<br>        &#39;VND.com.blackrock.Request-ID&#39;: str(uuid.uuid1())<br>    }<br>    return headers<br>     <br>#populate filter parameters<br> <br>params = {<br>  &quot;query&quot;: {<br>    &quot;portfolioGroupCriterion&quot;: {<br>      &quot;portfolioGroupTicker&quot;: &quot;TST-TRD1&quot;<br>    }<br>  }<br>}<br> <br>url = f&#39;https://&#39;+client_env+&#39;.blackrock.com/api/trading/order-management/order/v1/orders:filter&#39;<br>r = requests.post(url,auth=(username,pwd),json=params,headers=generate_headers()).json()<br>print(r)</pre><p>A quick analysis of the above code snippet helps us identify some key pain-points:</p><ul><li>Setup/configuration and boilerplate code is verbose</li><li>Users are required to construct and provide header fields such as Authorization, Origin-Timestamp and Request-ID</li><li>Users are required to know the complete hostpath</li><li>Any changes to the API path/configurations in the future would require all users to also update their code</li><li>Users would need to validate response data types</li></ul><p>Now, let’s look at the same request using the Aladdin SDK:</p><pre>from aladdinsdk.api import AladdinAPI<br> <br>api_instance_order = AladdinAPI(&quot;OrderAPI&quot;)<br>request_body = {<br>    &quot;query&quot;: {<br>        &quot;portfolioGroupCriterion&quot;: {<br>            &quot;portfolioGroupTicker&quot;: &quot;TST-TRD1&quot;,<br>        }<br>    }<br>}<br>orders_res = api_instance_order.post(&quot;/orders:filter&quot;, request_body)</pre><p>Configurations can be provided in the following ways:</p><ol><li>(Preferred) Using a configuration file. These configurations apply to all API calls in the script:</li></ol><pre>export ASDK_USER_CONFIG_FILE=&lt;file path pointing to below file&gt;</pre><pre>RUN_MODE: local<br>API:<br>  AUTH_TYPE: Basic Auth<br>  TOKEN: &lt;api key from studio UI&gt;<br> <br>USER_CREDENTIALS:<br>  USERNAME: &lt;uname&gt;<br>  ENCRYPTED_PASSWORD_FILEPATH: &lt;path to file containing encrypted secret&gt;  <br>    # Password is optional while working locally since SDK can store and <br>    # retrieve this secret from OS&#39;s Credential Manager</pre><p>2. Environment variables can be used to override configurations given via the config file. These configurations also apply to all API calls in the script:</p><pre>export ASDK_RUN_MODE=local<br>export ASDK_API__TOKEN=&lt;api key from studio UI&gt;<br>export ASDK_API__AUTH_TYPE=&quot;Basic Auth&quot;<br>export ASDK_USER_CREDENTIALS__USERNAME=&lt;username&gt;<br>export ASDK_USER_CREDENTIALS__ENCRYPTED_PASSWORD_FILEPATH=&lt;secret filepath&gt;</pre><p>3. Configurations can also be simply provided in line, for when users want to try a different configuration, but not disturb the setup for the whole script. For example:</p><pre>from aladdinsdk.api import AladdinAPI<br><br>api_instance_order = AladdinAPI(&quot;OrderAPI&quot;, api_key=&quot;&lt;from studio UI&gt;&quot;)</pre><p>Some key advantages of this approach are:</p><ul><li>Users can setup API configuration once and have that available for all API calls in their code</li><li>Authentication, retry and error handling can be performed by AladdinAPI class — configurations help tweak the behavior of each functionality</li><li>Response validation is done by OpenAPI generated code</li></ul><h3>What about other authentication mechanisms?</h3><p>In the above example, we used ‘Basic Auth’ and kicked off a ‘Long Running Operation’. What if we want to use a different authentication mechanism? Or follow up the action by a polling the LRO till the result is computed?</p><p><em>To switch from Basic Auth to OAuth:</em></p><p>Without the Aladdin SDK: Users would now have to setup more boilerplate code to use OAuth credentials and communicate with the Auth Server to get an “OAuth Access Token”. This would also require more educational material to be distributed for some users, and support them through the Basic Auth to OAuth migration process.</p><p>With the Aladdin SDK: As some eager readers might have already pointed out, Authentication Type is a configurable attribute on the SDK. Users would simply need to change their configurations to look something like this:</p><pre>RUN_MODE: local<br> <br>API:<br>  AUTH_TYPE: OAuth<br>  OAUTH:<br>    CLIENT_ID: &lt;oauth client ID&gt;<br>    CLIENT_SECRET: &lt;oauth client secret&gt;<br>    REFRESH_TOKEN: &lt;oauth user refresh token&gt;</pre><p>Note: Additional configuration attributes let users point to files containing the secrets/tokens so sensitive information is not part of configuration files.</p><p><em>Long Running Operation (LRO) utility:</em></p><p>Without the Aladdin SDK, users must perform the following additional steps to retrieve the results of a long running operation:</p><ul><li>Parse first API response to get the LRO-ID</li><li>Identify the LRO status check endpoint</li><li>Set up a polling loop</li><li>At responsible intervals, invoke the status check endpoint till the LRO is complete</li></ul><p>With the Aladdin SDK: Simply invoke the awaitable call_lro_api method on the API wrapper, and provide the LRO status check endpoint path as a parameter. The SDK performs the steps to wire the LRO ID to the status check endpoint, and periodically ping the status check endpoint to see if the LRO has completed.</p><pre>import json<br>import asyncio<br>from aladdinsdk.api import AladdinAPI<br>from aladdinsdk.common.datatransformation import json_to_pandas<br> <br>def process_lro_response(lro_response):<br>    &quot;&quot;&quot;<br>    Callback function to process the LRO response<br>    &quot;&quot;&quot;<br>    print(&quot;===========================DONE==============================&quot;)<br>    json_object = json.dumps(lro_response[&#39;response&#39;], indent=8)<br>    df = json_to_pandas.convert(json_object, &quot;results.[*].output.order&quot;)<br>    print(df)<br> <br>api_instance_order = AladdinAPI(&quot;OrderAPI&quot;)<br>orders_payload = {<br>    &#39;postOrderConfig&#39;: { &#39;whatIf&#39;: False, &#39;runCompliance&#39;: True },<br>    &#39;orders&#39;: [{<br>        &quot;transactionType&quot;: &quot;BUY&quot;,<br>        &quot;assetId&quot;: &#39;TYU520157&#39;,<br>        &quot;orderDetails&quot;: [{<br>            &quot;portfolioReference&quot;: { &quot;portfolioTicker&quot;: &quot;TST-TRD1&quot; },<br>            &quot;quantity&quot;: 1000.0<br>        }]<br>    }]<br>}<br>lro = api_instance_order.call_lro_api(<br>   start_lro_endpoint=((&quot;/orders:batchPost&quot;, &quot;post&quot;)),  # start endpoint<br>   request_body=orders_payload,  # start_lro_endpoint request body<br>   check_lro_status_endpoint=((&quot;/longrunningoperations/{id}&quot;, &quot;get&quot;)),<br>       # endpoint to check status<br>   _deserialize_to_object=False,<br>   callback_func=process_lro_response)<br>   # Note: if no callback function provided, response is returned as is<br> <br>asyncio.run(lro)</pre><p>Note: This functionality is more flexible with configurations/utilities such as setting polling frequency, timeouts, retries and callback functions.</p><h3>Connecting to the Aladdin Data Cloud (ADC)</h3><p>Continuing with the compare and contrast approach, let’s take an example of connecting to the Aladdin Data Cloud — Aladdin’s cloud-hosted data and analytics platform built on Snowflake technology — to make a simple query and read the data into a pandas dataframe.</p><p>Below is the conventional method to create an ADC connection:</p><p>First, users must set up an RSA Key. Next, these steps should be carried out within the user session to execute a SQL query.</p><pre># Set import libraries<br>import os<br>import snowflake.connector<br>import pandas as pd<br>from cryptography.hazmat.backends import default_backend<br>from cryptography.hazmat.primitives import serialization<br> <br>os.environ[&#39;NO_PROXY&#39;] = os.environ[&#39;no_proxy&#39;] = &#39;.snowflakecomputing.com&#39;<br><br>passphrase = get_credentials(&quot; &quot;, &quot;clientEnv&quot;)[&#39;rsa_passphrase&#39;]<br>#Add the path to your key<br>user = &#39;enter username&#39;<br>acct = &#39;enter URL&#39;<br> <br>with open(&quot; &quot;, &quot;rb&quot;) as key:  # path to key<br>    p_key = serialization.load_pem_private_key(<br>        key.read(),<br>        password=passphrase.encode(),<br>        backend=default_backend()<br>    )<br> <br>pkb = p_key.private_bytes(<br>    encoding=serialization.Encoding.DER,<br>    format=serialization.PrivateFormat.PKCS8,<br>    encryption_algorithm=serialization.NoEncryption())        <br> <br>     <br>ctx_read = snowflake.connector.connect(<br>    user=user,<br>    account=f&#39;blackrock-{acct}&#39;, <br>    private_key=pkb,<br>    role=&#39;ADC_ADMIN_ROLE&#39;,<br>    warehouse=&#39;CLI_STUDIO_WH&#39;,<br>    database=&#39;ALADDINDB&#39;,<br>    schema=&#39;INVESTMENTS&#39;<br>)<br> <br>df_pos = pd.read_sql(&quot;select * from pos_analytics LIMIT 10&quot;, ctx_read)</pre><p>Immediately, we can identify the following pain points:</p><ul><li>Verbose setup and configuration</li><li>Users are required to provide sensitive information such as their credentials/RSA token in code</li><li>Users are required to be aware of the account value (which might change in the event of a DR scenario)</li></ul><p>The same query with the Aladdin SDK is much simpler:</p><ul><li>The Aladdin SDK can infer most of the connection parameters from the ‘defaultWebServer’, but users are free to override all default configurations using one of the methods discussed above</li><li>Authentication is done using OAuth under the hood, by making an API call to Aladdin’s TokenAPI</li></ul><pre>from aladdinsdk.adc import ADCClient<br> <br>adc_client = ADCClient()<br> <br>df_pos = adc_client.query_sql(&quot;select * from pos_analytics LIMIT 10&quot;)</pre><p>The above examples provide just a glimpse of the abstractions and utilities built into the Aladdin SDK. For more detailed documentation of each feature, please read the <a href="https://github.com/blackrock/aladdinsdk/blob/main/README.md">Aladdin SDK README.md</a></p><h3>Building the Foundation for Domain-Specific SDKs</h3><p>The core Aladdin SDK is designed with extensibility in mind. It provides a foundational structure and allows for the development of domain-specific SDKs tailored to unique quantitative needs and investment workflows. This modular approach allows the community to build on the existing framework, creating a rich ecosystem of specialized tools that cater to specific financial domains such as algorithmic trading, risk management, or portfolio optimization.</p><p>Domain-specific SDKs also have access to the same configuration management tools as the core Aladdin SDK, so developers are encouraged to implement their features using <a href="https://12factor.net/config">configurations</a>. These SDKs can have their own configuration settings for fine-tuning feature-set provided by the SDK.</p><h4>Open Sourcing Aladdin SDK</h4><p>BlackRock relies heavily on open-source software. With the intention of giving back to the open-source community, and inviting the developers to contribute to and build on top of this project, Aladdin SDK is open-sourced and available at <a href="https://github.com/blackrock/aladdinsdk">https://github.com/blackrock/aladdinsdk</a>.</p><h4>Conclusion</h4><p>The Aladdin SDK offers a standardized, user-friendly, and extensible solution for Python developers to get started building faster. By empowering citizen developers and providing a solid foundation for the creation of domain-specific SDKs, it aims to reshape the landscape of quantitative finance applications. As the financial industry continues to evolve, the Aladdin SDK demonstrates our commitment to the power of open, collaborative, and innovative solutions in the quest for a quantitative edge.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7f63376061e6" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/open-sourcing-the-aladdinsdk-empower-python-developers-with-a-quantitative-edge-7f63376061e6">Open Sourcing the Aladdin SDK: Empowering Python Developers with a Quantitative Edge</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Think Like an Octopus (in Python)]]></title>
            <link>https://engineering.blackrock.com/think-like-an-octopus-in-python-a4beb65e04e3?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/a4beb65e04e3</guid>
            <category><![CDATA[numpy]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[parallel-computing]]></category>
            <category><![CDATA[financial-services]]></category>
            <category><![CDATA[quantitative-finance]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Thu, 21 Mar 2024 13:07:24 GMT</pubDate>
            <atom:updated>2024-03-21T13:07:24.168Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bZYCUHvJ9MVVHZAgaVzeyg.jpeg" /></figure><h4>An octopus can move its tentacles in parallel. Michael Ziegltrum shows different tactics to enable your Python programs to move similarly.</h4><blockquote>By: <strong>Michael Ziegltrum</strong>, CFA, Vice President in Core Portfolio Management Architecture team</blockquote><p>Pirates of the Caribbean 2: Dead Man’s Chest had some memorable scenes. One that stood out featured the pirate captain Davy Jones <a href="https://www.youtube.com/watch?v=L0JbaZtoKAs">playing his organ</a>. Jones is part octopus-part man and in the scene his tentacles moved synchronously over the instrument playing chords. Each appendage worked together to deliver something bigger. What if our Python programs could do similarly?</p><p>In fixed income portfolio management, there are many interesting problems where we could apply these ideas. In exchange traded funds (ETFs) portfolio managers (PMs) often evaluate a list of orders and compare them to the portfolio. For example, a PM might look at a list of orders and see what percent of the bonds are from companies in the banking sector. She might compare this metric in the list of orders to the portfolio’s allocation to banking. This is one metric, but the comparisons needed could be extensive. Hence the evaluation is a non-trivial piece of computation. As the scale increases via number of sectors, bonds, and portfolios, the time and memory usage to calculate metrics on an order list could be a problem. One idea is to evaluate multiple metrics at once like an octopus moving its tentacles in parallel. There are many different options towards this in Python including libraries like threading, multiprocessing, asyncio, ctypes, numpy, and more, each with varying trade-offs.</p><p>This post supplements an earlier post, <a href="https://engineering.blackrock.com/citizen-developer-cookbook-python-multiprocessing-3dc3c8cab29a">“Citizen Developer Cookbook: Python Multiprocessing”</a>, from a colleague, Casey Clements, who wrote up some in-depth recipes focusing on aggregation, monitoring, and exception handling with multiprocessing. In this post, we’ll ignore those important topics having been well-covered in Casey’s excellent post.</p><h3>Executive Summary</h3><p>This blog post looks at various options for calculating the market value percent of a sector in an ETF across many funds and sectors. This includes: the Python <a href="https://docs.python.org/3.9/library/threading.html">threading</a> API; the Python <a href="https://docs.python.org/3.9/library/multiprocessing.html">multiprocessing</a> API; a second approach to using the Python multiprocessing API; writing the processing in C++ and calling from Python; and using <a href="https://numpy.org/doc/stable/">numpy’s</a> vectorization. There are various trade-offs between these methods including simplicity, CPU and memory usage, runtime, and code safeness. Any production usage would need to weigh these trade-offs, but, in this toy problem, numpy is the winner.</p><p>Many of these trade-offs will not come as a surprise to savvy readers but in short: Python threading can only use one cpu; starting a new process using Python multiprocessing is heavy-weight and relatively slow vs starting a thread; we can use more than one cpu by writing C++ code and calling it from Python, with more complex and risky code; or we can leverage numpy which has its own C wrappers and vectorization that is probably better than what we would write.<br> <br>Now, let’s get into it!</p><h3>Problem Setup</h3><p>Let’s say we want to write a program to take a list of orders and calculate how each sector’s market value compares to a given target.<br> <br> Let’s model a portfolio and sector as per the below. This corresponds to a spreadsheet where each row is a bond and there are columns for market value percent and sector. If a bond is worth $5, in a portfolio valued at $100, that bond has a 5% market value percent. If that bond and another bond with market value of 2% are the only two bonds from companies in the banking sector, the banking sector allocation in the portfolio is 7%. In a real portfolio we would probably need to store identifiers for the bonds as well as other pieces of info, but we’ll ignore it in this toy example.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*KN9lGG0HCkHCCPkmY-nBMQ.png" /></figure><p>If you want a runnable script to follow along, a link is included at the bottom of this post.</p><pre>@dataclass<br>class Portfolio:<br>    &quot;&quot;&quot;<br>    Container class to keep track of market values weights and sectors for a portfolio.<br>    &quot;&quot;&quot;<br>    market_value_pcts: &quot;np.ndarray&quot;<br>    sectors: &quot;np.ndarray&quot;<br> <br> <br>class Sector(Enum):<br>    &quot;&quot;&quot;<br>    Enumeration to store sector information for bonds in a portfolio.<br>    &quot;&quot;&quot;<br>    BANKING = 0<br>    BASIC_INDUSTRY = 1<br>    BROKERAGE_ASSET_MANAGERS = 2<br>    CAPITAL_GOODS = 3<br>    COMMUNICATIONS = 4<br>    CONSUMER_CYCLICAL = 5<br>    CONSUMER_NON_CYCLICAL = 6<br>    ELECTRIC = 7<br>    ENERGY = 8<br>    FINANCE_COMPANIES = 9<br>    FOREIGN_AGENCIES = 10<br>    INSURANCE = 11<br>    NATURAL_GAS = 12<br>    OTHER_INDUSTRY = 13<br>    OTHER_UTILITY = 14<br>    REIT = 15<br>    TECHNOLOGY = 16<br>    TRANSPORTATION = 17</pre><p>We can instantiate some mock data with the below. Note that for each bond we store a 64-bit float and a 32-bit int. So, if we have 50,000 bonds, we store about 50,000 * (64 + 32) = 4,800,000 bits, or about 0.5MB. If we have 20 portfolios, this leads us to 10MB. On some Linux varieties, the limit to memory that we can use on the stack as local variables is 8MB, meaning for this data structure we would need to allocate memory from the heap. As such, this is a non-trivial chunk of data.</p><pre>def prepare_mock_portfolio(num_bonds: int) -&gt; Portfolio:<br>    &quot;&quot;&quot;<br>    Helper function to prepare some fake portfolio data<br>    @param num_bonds: how many bonds to put in the fake portfolio<br>    @return:<br>    &quot;&quot;&quot;<br>    market_value_pcts = np.random.uniform(size=(num_bonds,)).astype(np.float64)<br>    market_value_pcts /= np.sum(market_value_pcts)<br>    sectors = np.random.randint(low=0, high=len(Sector), size=(num_bonds,), dtype=np.int32)<br>    portfolio = Portfolio(market_value_pcts, sectors)<br>    return portfolio<br> <br> <br>num_bonds = 50000<br>num_portfolios = 20<br>portfolios = []<br>for i in range(num_portfolios):<br>    portfolios.append(prepare_mock_portfolio(num_bonds))</pre><p>Let’s assume we want to calculate the sector weights vs targets for each portfolio, though in reality we may have more complex rules:</p><pre>def log_tolerance(worker_num: int, sector: Sector, market_value_pct: float, target_weight: float):<br>    print(<br>        f&quot;worker: {worker_num} {sector.name}: {market_value_pct:.3f}, target_weight: {target_weight:.3f}, &quot;<br>        f&quot;diff_from_tol: {(market_value_pct - target_weight):.3f}\n&quot;)<br> <br> <br>def calculate_sector_weights(worker_num: int, portfolio: Portfolio, target_weight: float):<br>    &quot;&quot;&quot;<br>    Function to calculate how much market value is in each sector and log this for a given portfolio.<br>    @param worker_num: which worker is responsible for the calculation<br>    @param portfolio: portfolio container<br>    @param target_weight: a target weight to compare against market value pct<br>    @return:<br>    &quot;&quot;&quot;<br>    sector_weights = [0.0] * len(Sector)<br> <br>    for i in range(portfolio.sectors.shape[0]):<br>        bond_sector = portfolio.sectors[i]<br>        sector_weights[bond_sector] += portfolio.market_value_pcts[i]<br> <br>    for i in range(len(Sector)):<br>        sector = Sector(i)<br>        mv_pct = sector_weights[i]<br>        log_tolerance(worker_num, sector, mv_pct, target_weight)<br>    return</pre><h3>Examining Approaches</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m4RgMmemK8XJCi7F4ppBow.jpeg" /></figure><p>All of the below approaches were run on Linux (Debian bullseye) and Python 3.9.18. Example output looks like the below:</p><pre>...<br>worker: 0 TECHNOLOGY: 0.054, target_weight: 0.056, diff_from_tol: -0.002<br> <br>worker: 0 TRANSPORTATION: 0.055, target_weight: 0.056, diff_from_tol: -0.000<br>...</pre><h3>Iterative Approach</h3><p>As we look at ways to solve this problem, the first thing that may come to mind is the iterative approach:</p><pre>def iterative_calcs(portfolios: List[Portfolio], target_weight: float):<br>    &quot;&quot;&quot;<br>    Iteratively calculate and log the market value weights for each sector in each portfolio.<br>    @param portfolios: list of Portfolio instances<br>    @param target_weight: target sector weight to compare actual weight to<br>    @return:<br>    &quot;&quot;&quot;<br>    for pf in portfolios:<br>        calculate_sector_weights(0, pf, target_weight)</pre><p>This runs in about 0.54 seconds and during the run the most virtual memory used was 474MB. The most CPU used was 100%. The relevant PID is 94 — I’m using a wrapper to run and profile these (PID 82 in the below). This wrapper starts up a subprocess to run the sector calculations according to a given methodology like threading or multiprocessing and it watches the resource usage as it does so.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/698/1*Vc-MM3Uv8S1Zp3EyW1rNgQ.png" /></figure><p>Can we do better than this?</p><h3>Threading Approach</h3><p>In a lot of programming languages threads let us use do more than one thing at a time while sharing the same memory. Could we evaluate more than one rule at once using <a href="https://docs.python.org/3.9/library/threading.html">threading</a>?</p><pre>def threading_calcs(num_workers: int, portfolios: List[Portfolio], target_weight: float):<br>    &quot;&quot;&quot;<br>    Calculate and log the market value weights for each sector in each portfolio using one thread per portfolio.<br>    @param num_workers: how many threads to use at a given time. for example, 3.<br>    @param portfolios: list of Portfolio instances<br>    @param target_weight: target sector weight to compare actual weight to<br>    @return:<br>    &quot;&quot;&quot;<br>    for pf_index in range(0, len(portfolios), num_workers):<br>        worker_list = []<br>        for thread_index in range(num_workers):<br>            pf_index = pf_index + thread_index<br>            if pf_index &gt;= len(portfolios):<br>                continue<br>            args = (thread_index, portfolios[pf_index], target_weight)<br>            worker = threading.Thread(target=calculate_sector_weights, args=args)<br>            worker_list.append(worker)<br>            worker.start()<br>        [worker.join() for worker in worker_list]</pre><p>This runs in about 0.52 seconds and during the run the most virtual memory used was about 667MB. The most CPU used was 113%.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/698/1*0fCcE9k1VHrYxyzG3zGHYQ.png" /></figure><p>Compared to the iterative approach we use 200MB more memory. We have improved run time by only 0.02 seconds, though. We may have expected to cut run time down by a third, given we use 3 workers. What gives?<br> <br> The key is in our CPU usage which is also around 100%. If we were truly doing multiple things at a time, this would have been 300% (representing using 100% of 3 CPU cores). Python, as a language, though, has many built-in features to protect Python programmers from people like themselves.</p><p>One such mechanism is the global interpreter lock (GIL) which makes it so that only one thread can access the interpreter and do something at a given point in time. Many third-party libraries like numpy have C code that circumvents this, but if we are using pure Python we cannot get around this lock.</p><p>In future versions of Python, we may escape the GIL (see discussion <a href="https://discuss.python.org/t/a-steering-council-notice-about-pep-703-making-the-global-interpreter-lock-optional-in-cpython/30474">here</a>), but, for now, threading is not a good fit for CPU bound tasks like this one. It <em>may</em> have been a better fit if we had moved some of these calculations to a microservice that we could then call via an http or other call.<br> <br> So, why was the CPU usage slightly over 100%? Probably the thread switching, which may use more than 1 CPU, but I leave this for discussion.</p><h3>Multiprocessing Approach</h3><p>There is another Python library that can do more than one thing at a time. The <a href="https://docs.python.org/3.9/library/multiprocessing.html">multiprocessing</a> library starts up processes. Processes are heavier weight than threads and do not share memory space, so we will have to worry about passing data back and forth between processes. That said, the API was designed to be similar to the threading API so an initial implementation might look like the below:</p><pre>def multiprocessing_calcs_bad(num_workers: int, portfolios: List[Portfolio], target_weight: float):<br>    &quot;&quot;&quot;<br> <br>    @param num_workers: how many processes to use at a given time. for example, 3.<br>    @param portfolios: list of Portfolio instances<br>    @param target_weight: target sector weight to compare actual weight to<br>    @return:<br>    &quot;&quot;&quot;<br>    for pf_index in range(0, len(portfolios), num_workers):<br>        worker_list = []<br>        for thread_index in range(num_workers):<br>            pf_index = pf_index + thread_index<br>            if pf_index &gt;= len(portfolios):<br>                continue<br>            args = (thread_index, portfolios[pf_index], target_weight)<br>            worker = multiprocessing.Process(target=calculate_sector_weights, args=args)<br>            worker_list.append(worker)<br>            worker.start()<br>        [worker.join() for worker in worker_list]</pre><p>This runs in about 0.23 seconds, a bit better than half the run time of the iterative or threading approaches. Why is this? We can now do more than one thing at a time, like an octopus playing a church organ.</p><p>In the screenshot below we can see that we have spawned more Python processes, and each of those processes (PID 351, 352, 353) can use up to a full CPU. As an aside, in the screenshot we only see 6.7%. This is because I took the screenshot at the wrong time. In reality they probably used 100% CPU for a brief instant then exited.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/698/1*zQHsz9B_FFtCWrJDpoXd-g.png" /></figure><p>However, this is at the cost of using significantly more memory (perhaps 1.5 gigabytes more) than prior approaches because each process has to have a Python setup and that large mock data that we are working with.<br> <br> Another drawback is that we again may have expected to cut down run time to a third of the iterative approach given we use three workers, but only cut it in half. What gives?<br> <br> Well, starting up a process is an intensive thing, and in the above code we start up one process for each portfolio. Additionally, sending data to the new process like our portfolio list can take time.<br> <br>Can we do better?</p><h3>Multiprocessing Approach With Fixed Workers</h3><p>With what I admit is a non-trivial amount of new code, we can bring up 3 workers and leave them up throughout the program, sending jobs to them. This should save process start up time. We also will copy the portfolio list just once to each of the three processes, rather than sending it once per portfolio. In this example I don’t expect this latter bit to save a lot of time here but it could be more relevant for you depending on size and structure of your data.</p><pre>@dataclass<br>class MPCalcSectorWeightArgs:<br>    &quot;&quot;&quot;<br>    Container class for arguments to a multiprocessing worker to calculate portfolio weights.<br>    &quot;&quot;&quot;<br>    exit_flag: bool<br>    pf_index: int<br> <br> <br>def calc_sector_weights_wrapper(args_queue: Queue, worker_num: int, portfolios: List[Portfolio], target_weight: float):<br>    &quot;&quot;&quot;<br>    Helper function to bring up a worker and keep it up looking for new jobs to run in the queue. Will exit when it<br>    receives a job with an exit flag set in the queue.<br>    @param args_queue: a queue to poll for jobs or to exit<br>    @param worker_num: which worker this is<br>    @param portfolios: list of Portfolio instances<br>    @param target_weight: target sector weight to compare actual weight to<br>    @return:<br>    &quot;&quot;&quot;<br>    exit_flag = False<br>    while not exit_flag:<br>        if args_queue.qsize() &gt; 0:<br>            args = args_queue.get()<br>            pf_index = args.pf_index<br>            exit_flag = args.exit_flag<br>            if exit_flag:<br>                continue<br>            calculate_sector_weights(worker_num, portfolios[pf_index], target_weight)<br> <br> <br>def multiprocessing_calcs(num_workers: int, portfolios: List[Portfolio], target_weight: float):<br>    &quot;&quot;&quot;<br>    Calculate and log the market value weights for each sector in each portfolio using the given number of workers and<br>    keeping these processes up throughout the run to save on process start up time.<br>    @param num_workers: how many threads to use at a given time. for example, 3.<br>    @param portfolios: list of Portfolio instances<br>    @param target_weight: target sector weight to compare actual weight to<br>    &quot;&quot;&quot;<br>    worker_list = []<br>    q_list = []<br>    for i in range(num_workers):<br>        q = multiprocessing.Queue()<br>        p = multiprocessing.Process(target=calc_sector_weights_wrapper, args=(q, i, portfolios, target_weight))<br>        worker_list.append(p)<br>        q_list.append(q)<br>    [p.start() for p in worker_list]<br>    for pf_index in range(0, len(portfolios)):<br>        args = MPCalcSectorWeightArgs(False, pf_index)<br>        worker_num = pf_index % num_workers<br>        q_list[worker_num].put(args)<br>    for worker_num in range(num_workers):<br>        args = MPCalcSectorWeightArgs(True, 0)<br>        q_list[worker_num].put(args)<br>    [worker.join() for worker in worker_list]</pre><p>How did we do? Runtime is now around 0.20 seconds. We saved 13% versus the prior implementation. This is 0.03 seconds which may not be significant in the scheme of the 2–3 years of a typical blue-ringed octopus’ life, but is a significant improvement in the computer world!</p><p>On memory, we did not improve nor did we expect to. With regards to CPU usage, as the processes below stay up longer than the processes in the prior example (given we only have 3 and we are sending jobs to them and keeping them alive), we see higher CPU usage. That said, this is only reflecting the fact that the processes are up longer and I have time to get the output from the top command.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/698/1*-jxzW2hoRjUeqQaeUqIZzA.png" /></figure><p>If we wanted to push start up time more, the multiprocessing API supports other ways to pass data to processes and we might find that some are faster than others (like using shared memory).</p><h3>C++ Wrappers and Recreating the Wheel</h3><p>We are doing quite a lot of work here to get around Python’s limitations on threading. Specifically, we are starting processes, passing data to them, and so on. But, Python has good integration with C and C++, and we could simply write our heavy-duty processing in C++ instead and use threading there, to avoid the Python global interpreter lock. We can create Python bindings for this using the library <a href="https://docs.python.org/3.9/library/ctypes.html">ctypes</a>. This is a lot riskier, but let’s look at an example.<br> <br> In the below we set up one function in C++ to start threads and call the second function which calculates and writes the result to an existing output array.</p><pre>/**<br> * Looks at the input pf_num argument and writes the sector weights of that portfolio to the portfolio_sector_weights argument.<br> * No size checking is done on the input pointers, so use this function with great caution. See argument descriptions<br> * for input pointer structure.<br> *<br> * @param  pf_num which portfolio to calculate, used to move to relevant section of memory in below pointers.<br> * @param  portfolio_bond_weights  pointer of size num_portfolios * num_bonds, flattened. Each entry is the market value percent of a bond.<br> * @param  portfolio_bond_sectors pointer of size num_portfolios * num_bonds, flattened. Each entry is the sector of a bond in integer representation.<br> * @param  num_bonds how many bonds per portfolio, assumed to be the same for each portfolio. Used to move to relevant memory section.<br> * @param  portfolio_sector_weights the output array to write portfolio sector weights to. Size of num_portfolios * num_sectors.<br> * @param  num_sectors the number of sectors in the sector scheme. Used to move to relevant section of memory in portfolio_sector_weights.<br> * @return      void<br> * @see         calculate_sector_weights<br> */<br>void worker_calculate_sector_weights(const uintmax_t pf_num, const float64_t *portfolio_bond_weights, const int32_t *portfolio_bond_sectors,<br>                                     const uintmax_t num_bonds,<br>                                     float64_t *portfolio_sector_weights, const uintmax_t num_sectors)<br>{<br>    const uintmax_t starting_out_index = pf_num * num_sectors;<br>    for (uintmax_t i = 0; i &lt; num_sectors; i++)<br>    {<br>        portfolio_sector_weights[starting_out_index + i] = 0.0;<br>    }<br> <br>    const uintmax_t starting_bond_index = pf_num * num_bonds;<br>    for (uintmax_t bond_index = starting_bond_index; bond_index &lt; starting_bond_index + num_bonds; bond_index++)<br>    {<br>        const int32_t &amp;sector = portfolio_bond_sectors[bond_index];<br>        const float64_t &amp;market_value_pct = portfolio_bond_weights[bond_index];<br>        portfolio_sector_weights[starting_out_index + sector] += market_value_pct;<br>    }<br>}<br> <br>extern &quot;C&quot;<br>{<br>    /**<br>     * Batches out calculations of sector weights in portfolios to threads. Uses a max of three threads at a time. Writes the sector weights to the<br>     *  portfolio_sector_weights argument. No size checking is done on the input pointers, so use this function with great caution.<br>     *  See argument descriptions for input pointer structure.<br>     *<br>     * @param  portfolio_bond_weights  pointer of size num_portfolios * num_bonds, flattened. Each entry is the market value percent of a bond.<br>     * @param  pf_size how many portfolios in input pointers, used to move to relevant section of memory in below pointers.<br>     * @param  portfolio_bond_sectors pointer of size num_portfolios * num_bonds, flattened. Each entry is the sector of a bond in integer representation.<br>     * @param  num_bonds how many bonds per portfolio, assumed to be the same for each portfolio. Used to move to relevant memory section.<br>     * @param  portfolio_sector_weights the output array to write portfolio sector weights to. Size of num_portfolios * num_sectors.<br>     * @param  num_sectors the number of sectors in the sector scheme. Used to move to relevant section of memory in portfolio_sector_weights.<br>     * @return      void<br>     */<br>    void calculate_sector_weights(const float64_t *portfolios_bond_weights, const uintmax_t pf_size,<br>                                  const int32_t *portfolios_bond_sectors, const uintmax_t num_bonds,<br>                                  float64_t *portfolios_sector_weights, const uintmax_t num_sectors)<br>    {<br>        const uintmax_t num_workers = 3;<br>        std::thread thread_arr[num_workers];<br> <br>        for (uintmax_t i = 0; i &lt; pf_size; i++)<br>        {<br>            if (i % num_workers != 0)<br>            {<br>                continue;<br>            }<br>            for (uintmax_t j = 0; j &lt; num_workers; j++)<br>            {<br>                uintmax_t pf_num = i + j;<br> <br>                if (pf_num &gt;= pf_size)<br>                {<br>                    continue;<br>                }<br>                thread_arr[j] = std::thread(worker_calculate_sector_weights, pf_num, portfolios_bond_weights, portfolios_bond_sectors,<br>                                            num_bonds, portfolios_sector_weights, num_sectors);<br>            }<br>            for (uintmax_t j = 0; j &lt; num_workers; j++)<br>            {<br>                if (i + j &gt;= pf_size)<br>                {<br>                    continue;<br>                }<br>                thread_arr[j].join();<br>            }<br>        }<br>    }<br>}</pre><p>We can load this into Python by using the ctypes library and create a wrapper around it to do some argument validation. Then we can call this function as we’ve called previous functions.</p><pre>def setup_cpp_lib():<br>  &quot;&quot;&quot;<br>  Helper function to load in the shared library object for the C++ functions and set some basic argument validation<br>   on the functions in the library. Relies on the .so file being generated and in the working directory<br>   of the python file.<br>  @return:<br>  &quot;&quot;&quot;<br>    cpp_lib = ctypes.cdll.LoadLibrary(&quot;./pf_sector_helpers.so&quot;)<br>    calculate_sector_weights = cpp_lib.calculate_sector_weights<br>    calculate_sector_weights.restype = None<br> <br>    calculate_sector_weights.argtypes = [ndpointer(ctypes.c_double, flags=&quot;C_CONTIGUOUS&quot;), ctypes.c_size_t,<br>                                         ndpointer(ctypes.c_int32, flags=&quot;C_CONTIGUOUS&quot;), ctypes.c_size_t,<br>                                         ndpointer(ctypes.c_double, flags=&quot;C_CONTIGUOUS&quot;), ctypes.c_size_t]<br>    return cpp_lib<br> <br> <br>def cpp_calc_sector_weights(portfolios: List[Portfolio], target_weight: float, cpp_lib):<br>  &quot;&quot;&quot;<br>  Python wrapper function for the C++ function to calculate sector weights. Calculates and logs the market value<br>   weights for each sector in each portfolio using one thread per portfolio.<br>  @param portfolios: list of Portfolio instances<br>  @param target_weight: target sector weight to compare actual weight to<br>  @param cpp_lib: shared library loaded using setup_cpp_lib<br>  @return:<br>  &quot;&quot;&quot;<br>    sector_array = np.concatenate([pf.sectors for pf in portfolios], axis=None, dtype=np.int32)<br>    mv_arrays = np.concatenate([pf.market_value_pcts for pf in portfolios], axis=None, dtype=np.float64)<br> <br>    if sector_array.shape != mv_arrays.shape:<br>        raise ValueError(&quot;sectors must have same number of entries as bonds&quot;)<br> <br>    num_portfolios = len(portfolios)<br>    num_sectors = len(Sector)<br>    num_bonds = portfolios[0].market_value_pcts.shape[0]<br>    out_arr = np.zeros((num_portfolios * num_sectors,), dtype=np.float64)<br> <br>    cpp_lib.calculate_sector_weights(mv_arrays, num_portfolios, sector_array, num_bonds, out_arr, num_sectors)<br> <br>    for pf_index in range(num_portfolios):<br>        row_index = pf_index * num_sectors<br>        for i in range(num_sectors):<br>            sector = Sector(i)<br>            log_tolerance(0, sector, out_arr[row_index + i], target_weight)</pre><p>This runs in about 0.06 seconds, a <em>substantial</em> improvement from our prior methods. In fact, I had to use a new method to examine CPU and memory usage because it ran too quickly for my prior method. We see CPU Usage between 200% and 400% at various times in the script. I am running on a computer with 6 Cores and 12 logical processors. Using 3 threads in C++ I would expect up to 300% in top used. In reality, I am measuring at a point in time and threads could be spinning up or down, so I don’t see exactly these numbers. That said, usage over 100% indicates we are doing more than one thing at a time. C++, unlike Python, does not have a global interpreter lock (GIL), though production multithreaded code employs various locks and synchronization techniques to protect shared variables and such.<br> <br> Memory usage is similar to the iterative method, given we are still using Python to call the C++ code. As an aside, at one point in the program CPU usage was 1200% (using each of my 12 logical processors) but this was numpy operations to make mock data and we will talk about numpy shortly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/698/1*lepml5b5t23e6sq9_s5C-A.png" /></figure><p>The C++ example achieves performance gains, but at the cost of significantly more complex code. Note that we had to work with pointers opening the door to segmentation faults and I didn’t do as much argument validation as I should have. I also played pretty fast and loose with shared resources in the threads. This is risky, there could be memory related bugs lurking just below the surface!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SErVln7zLjWpvoYvFUBPdQ.jpeg" /></figure><p>I wonder if someone already done an implementation of these calculations in C with some controls that also can take advantage of parallel computing?</p><h3>numpy</h3><p>The answer is yes, <a href="https://numpy.org/doc/stable/">numpy</a> has done a lot of work to enable fast vectorized calculations that take advantage of C and parallel processing. It also has a lot more controls and safeguards in place than the code I wrote above.</p><pre>def numpy_calc_sector_weights(portfolios: List[Portfolio], target_weight: float):<br>  &quot;&quot;&quot;<br>  Calculates and logs the market value weights for each sector in each portfolio using numpy to achieve parallel<br>   processing.<br>  @param portfolios: list of Portfolio instances<br>  @param target_weight: target sector weight to compare actual weight to<br>  @return:<br>  &quot;&quot;&quot;<br>    sector_array = np.array([pf.sectors for pf in portfolios], dtype=np.int32)<br>    mv_arrays = np.array([pf.market_value_pcts for pf in portfolios], dtype=np.float64)<br>    num_portfolios = len(portfolios)<br>    num_sectors = len(Sector)<br>    out_arr = np.zeros((num_portfolios, num_sectors,), dtype=np.float64)<br> <br>    for sector_index in range(num_sectors):<br>        sector_mask = np.equal(sector_array, sector_index)<br>        sector_sums = np.sum(mv_arrays, axis=1, where=sector_mask)<br>        out_arr[:, sector_index] = sector_sums<br> <br>    for pf_index in range(num_portfolios):<br>        for i in range(num_sectors):<br>            sector = Sector(i)<br>            log_tolerance(0, sector, out_arr[pf_index, i], target_weight)</pre><p>This runs in about 0.08 seconds, about the same as our self-written method. Notably, CPU usage is higher than in the C++ code I wrote, probably because numpy has different default limits set on the number of cores it can use at once (I set number of threads to 3 in my C++ example). That said, it was tricky for me to identify the CPU usage at the point in the function that does the sector calculations and so how much higher I’m not sure about. I’m sure we could set limits on this if we wanted to, even via docker if needed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/698/1*dgS_FtLKb8T1A9JXDc5uOA.png" /></figure><p>Other folks may be able to come up with a solution that leverages numpy to vectorize over sectors as well, rendering the for loop over sectors unnecessary and parallelized.<br> <br>Regarding controls and safeguards, in numpy if I try to access an element outside an array, I get an error. In the C++ code I wrote above I do not check sizes and do validation. Instead, I parse memory addresses to a double. If I access an out-of-bounds element due to bad input or a bug, I will read memory I didn’t intend to. Either this memory doesn’t belong to me and I will cause a seg fault or I will silently read something I didn’t intend to. Either are poor outcomes. Hence, the validation numpy does is useful.</p><pre>Python 3.9.18 (main, Feb  1 2024, 06:03:49)<br>[GCC 10.2.1 20210110] on linux<br>Type &quot;help&quot;, &quot;copyright&quot;, &quot;credits&quot; or &quot;license&quot; for more information.<br>&gt;&gt;&gt; import numpy as np<br>&gt;&gt;&gt; arr = np.array([1, 2])<br>&gt;&gt;&gt; arr[10]<br>Traceback (most recent call last):<br>  File &quot;&lt;stdin&gt;&quot;, line 1, in &lt;module&gt;<br>IndexError: index 10 is out of bounds for axis 0 with size 2</pre><p>There are other examples of controls and safeguards numpy does for us that I don’t go into here.</p><h3>Third Party Libraries</h3><p>There are other libraries for parallel computing in Python. My colleague, Casey Clements, who wrote the blog post that I mentioned <a href="https://engineering.blackrock.com/citizen-developer-cookbook-python-multiprocessing-3dc3c8cab29a">here</a> briefly mentions Dask, Airflow, and Prefect.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/320/1*3MU4PlHX8Dkq6zZ4Pb047g.png" /></figure><h3>Conclusion</h3><p>Rather like an octopus trying to ambush a clown fish in a coral reef, there are many ways to approach parallel computing in Python. If I was reviewing a pull request implementing parallel computing (or ideally brainstorming with colleagues <em>before</em> a pull request ever came out) there are a few factors I would weigh:</p><ul><li>What is our current tech stack? If we are mainly a Python team, I will be biased to look at Python libraries like numpy that handle implementations.</li><li>How many rules and how many bonds do we have? This will influence whether we prioritize approaches emphasizing memory, multiple CPUs, or other aspects. As the number of rules increases we get more benefit from the multiprocessing API, given we can amortize the process startup cost.</li><li>How fundamental and how scalable does the process need to be? For a library that we rely on extensively, that needs to run fast many millions of times a day, it may make sense to do a barebones C++ implementation.</li></ul><p>There are many other topics in threading that we didn’t discuss today like synchronization and deadlocks.<br> <br> Looking at the approaches in this blog, for this problem, numpy is the winner for me. This is because it has similar performance to barebones C++ but with more controls, safeguards, and features in place.<br> <br> I hope you all enjoyed this discussion. See full runnable source code examples in <a href="https://github.com/MZandtheRaspberryPi/think_like_an_octopus">this repo</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a4beb65e04e3" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/think-like-an-octopus-in-python-a4beb65e04e3">Think Like an Octopus (in Python)</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Introducing InGen]]></title>
            <link>https://engineering.blackrock.com/introducing-ingen-cb6a5083788c?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/cb6a5083788c</guid>
            <category><![CDATA[etl]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[pandas]]></category>
            <category><![CDATA[extract-transform-load]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Thu, 01 Feb 2024 13:35:20 GMT</pubDate>
            <atom:updated>2024-02-02T14:41:26.167Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*epOO4BKhE99la_Jn32ThFg.jpeg" /></figure><h4>An Open Source ETT (Extract, Transform, Transfer) Python Tool</h4><blockquote><em>By: </em><strong><em>Swarna Dhakad</em></strong><em>, Senior Engineering Team Director, Aladdin Wealth Tech, &amp; </em><strong><em>Piyush Ranjan</em></strong><em>, Engineer III, Aladdin Wealth Tech</em></blockquote><h4>From ETL to ELT to ETT</h4><p>Interfaces allow two different systems to exchange data between each other. An example of an interface is a file which contains data in rows and columns. Think of an Environmental, Social and Governance (ESG) data provider sending a daily feed of files containing security identifiers and their ESG scores to an asset manager. Overnight feeds of analytics data, custodial ingestion of accounts and positions are other examples where one system ‘interfaces’ with another by exchanging data in an agreed upon format. Even in the age of APIs and real-time, streaming data, such asynchronous data transfer is still an extremely prevalent use case in the fintech industry.</p><p>Therefore, a common requirement is to be able to generate these interfaces repeatedly and automatically. While the purpose of the data generated will vary as per the business logic, the fundamental operations needed to extract the data are typically within a finite set of data operations like reading data from different sources, performing some data massaging and writing it to its destination in a predefined format.</p><p>The financial industry, and specifically asset management, has focused a lot on the data integration problems and has built a lot of solutions for doing ETL (Extract, Transform, Load), ELT (Extract, Load, Transform) operations. Most of these solutions require high expertise and skills to use these tools, which requires investing a lot of time in learning and upskilling. Often, we do not need such high-end tools and operations and just want to extract data from some sources and write it in a format that can be ingested to the destination system. We call this pattern Extract, Transform, and Transfer (ETT). ETT can be thought of as extracting data from various sources, transforming it into the required format and sending it to another system through files or APIs.</p><p>This was a very common use case at BlackRock in the Aladdin Wealth Tech Business, where we integrate with several external sponsor platforms, custodians, and other systems within the firm. Most of these data exchanges are daily and happen overnight. In the spirit of not reinventing the wheel, we first used existing, available tools for several use cases. However, we began to feel that it would be better for us in the long term to separate the business logic of extraction from the process of extraction. It was time for a better wheel, perhaps?</p><h4>Introducing: InGen</h4><p>Our ideas led us to create a new solution, InGen, a Python-based command line tool that allows the user to generate interface files from various sources like databases, files, and HTTP APIs, without writing any new code. The process is completely config-driven, requiring only a configuration file in YAML format, which declares the data sources, the formatting operations to be applied on the data, validations, and the output format.</p><p>An important aspect of InGen is that the configuration files are easy to write and can be built and maintained by non-developers, as well. This allows for a much broader set of users that can create their own complex data extracts without needing to involve a software team or write a single line of code.</p><p>This tool has been built on top of <a href="https://pandas.pydata.org/docs/">Pandas</a> and <a href="https://greatexpectations.io/">Great Expectations</a> library. The data from the defined sources is read into a pandas dataframe and goes through a series of transformations as described in the pre-processing and formatting stages of the configuration file. The data transformations can be thought of as a pipeline which you construct.</p><p>Pre-processing steps combine data from multiple sources — SQL like joins, concatenation of multiple data frames or filtering of duplicates can be performed in this stage. At the end of this stage, we should have a single data frame which is then passed to the next stage where column level formatting is applied.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LfLBYGdiOllDWRh_OLt62w.png" /></figure><p>Once the data has gone through all these steps, the last step is to write it. The most common example is to write this data to a file in tabular structure. However, InGen also supports transforming data into JSON and pushing it to a web API.</p><h4>Real-World Example</h4><p>An example of where we used InGen was for a project to integrate one of our operational processes with an external system. We needed to pick up an Excel file sent to us, translate it into a specific JSON format that the external system can ingest, and do some validations along the way. This was solved by using InGen’s file reader, JSON formatter, API writer and source validations.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7udPeGVZB9RyqExmvVwLzg.png" /></figure><p>Here, you can see the config which includes the data source, pre-processing steps on the source, and the desired output format.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/890/1*S7LvuQJeQugk_VBwkjtSkQ.png" /></figure><p>Below shows the destination configuration, which is where we want to send the data. Here, we configure the URL of the API that we are calling and the authentication method.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/705/1*Da3Q5I_Rzx3tYos1ppnJVg.png" /></figure><p>Finally, below shows the formatting steps where translation happens from the source to the destination. Here the column ‘Residence State’ in the source is being renamed to ‘State’. The ‘Portal Date’ column is being renamed to ‘Date’ in the destination as well as undergoing a date formatting.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/564/1*UuRRDbibnSJJzTSAVg_dUw.png" /></figure><h4>Open Sourcing InGen</h4><p>BlackRock relies heavily on open-source software and increasingly aims to give back to the open-source community. To realize the full potential of this configurable command line tool we are making InGen an open-source project with two goals in mind:</p><ol><li>Allow others to use this tool for their interface generation processes.</li><li>Welcome the open-source community to contribute to and enhance this project.</li></ol><p>To use InGen, follow the guidelines in the <a href="https://github.com/blackrock/ingen#installation">Getting Started</a> section of the README and to contribute to this project checkout our <a href="https://github.com/blackrock/ingen/issues">open issues</a> or <a href="https://github.com/blackrock/ingen/blob/main/CONTRIBUTING.md">contribution guidelines</a>.</p><p>Learn more about <a href="https://www.blackrock.com/aladdin">Aladdin</a> and <a href="https://careers.blackrock.com/life-at-blackrock-2/technology/">technology careers at BlackRock</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cb6a5083788c" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/introducing-ingen-cb6a5083788c">Introducing InGen</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How BlackRock’s hackathon empowers employees to be innovative]]></title>
            <link>https://engineering.blackrock.com/how-blackrocks-hackathon-empowers-employees-to-be-innovative-8ba3fb85a848?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/8ba3fb85a848</guid>
            <category><![CDATA[innovation]]></category>
            <category><![CDATA[engineering-mangement]]></category>
            <category><![CDATA[tech-culture]]></category>
            <category><![CDATA[corporate-culture]]></category>
            <category><![CDATA[hackathons]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Tue, 19 Dec 2023 21:42:07 GMT</pubDate>
            <atom:updated>2024-02-01T13:38:18.640Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*AwhkssV5KAbwAz4E_xs8iw.png" /></figure><blockquote><em>By: Our partners at </em><a href="https://www.themuse.com/profiles/blackrock"><em>The Muse</em></a></blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/1*1rZ4JnINPlg14KZc5RP-iw.png" /></figure><p>Mael Pidjou was barely six months into his role as an Associate on our Aladdin Client Business team when a manager asked him if he might have an idea for HACK:BLK, the firm’s annual hackathon creates the opportunity for creative problem solving through technology. And despite being a fresh face at the firm, Mael had just the thing.</p><p>In his role, the Tokyo-based Mael works closely on <a href="https://www.blackrock.com/aladdin">Aladdin</a>, BlackRock’s investment management technology, often delving into the firm’s deep reservoirs of data to find pertinent information for clients. When generative forms of AI, such as ChatGPT, began to dominate the news this year, Mael wondered: What if this kind of machine learning could search the database more easily, freeing up more time to tackle new projects?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/0*Ks66YjcnXQpPLISW.jpg" /></figure><p><em>Mael Pidjou, Associate, Tokyo</em></p><p>“Almost every person I had discussed my problem with said it was a pain point for them, too. In general, everyone wants to find information faster,” Mael says.</p><p>That inspiration became the basis of Smart Sensei, the winning project of the 2023 edition of HACK:BLK. The hackathon, which will celebrate its 10th anniversary in 2024, invites employees to collaborate with colleagues from their region, including some they may never have met. Regional teams are given a short time to virtually plan a project and just two days of around-the-clock hacking to bring an idea to fruition. Then, finalists create video presentations to showcase their prototypes, and employees vote to select one winning project.</p><p>Mimi Narbonne, a Vice President on the Aladdin Engineering Chief Operating Officer team, says the hackathon is a perfect example of one of the firm’s guiding principles, ‘One BlackRock.’ The concept refers to the power of working collaboratively and building connections across the company — from engineering and communications to sales and legal — to get things done.</p><p>“The hackathon wouldn’t happen without the whole firm being involved because the purpose is to think innovatively, and we have to bring everyone together to do that,” Mimi says.</p><p>Mael’s 2023 hackathon team is an illustrative example. The team came together during a HACK:BLK meeting in the Asia-Pacific region, where Mael and other employees pitched their ideas and formed small groups.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/300/0*fJlePS_Wld59vJwX.jpg" /></figure><p><em>Mimi Narbonne, Vice President, New York</em></p><p>There were two other members from the Tokyo office (which inspired Smart Sensei’s name) plus three colleagues from India, and everyone came from different teams within those offices. An Analyst had studied a little machine learning in college and was keen to see it applied in the real world. Two engineers were experts in machine learning and language models. And a Director who joined provided project management experience in taking an idea all the way from A to Z.</p><p>“The hackathon really proved to me that BlackRock has so many skillful people all over and that it’s possible to collaborate with different teams and different regions,” Mael says.</p><p>According to Mimi, HACK:BLK is a way to not only inspire grassroots ideas that could improve the firm’s operations, but also create a culture of innovation in which employees from every division — not just engineers — can hack, too. In fact, hackathon teams are often made up of employees with engineering skills as well as those who want to contribute in non-technical ways. The event also shows employees that smart solutions can come from anyone, rather than just top-down.</p><p>“We’re spreading our tech culture across the firm,” Mimi says. “We’re inviting everyone to bring their ideas, and then connecting them with engineers to take their ideas across the finish line because, in the end, you need a working prototype.”</p><p>“Most people on the technology side are well aware of what’s expected from a hackathon, but we want to encourage employees on the business side to get involved, too, and to think like technologists,” adds Jing Chen, a Director on the Aladdin Product Service Engineering team based in Atlanta and one of the six 2023 hackathon directors.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/199/0*Op9QPTEfwSLm7qOh.jpeg" /></figure><p><em>Jing Chen, Director, Atlanta</em></p><p>In previous years, HACK:BLK asked teams to innovate within a specific category or field such as cloud, blockchain, or sustainability. But in 2023, the directors took a different approach, choosing a broader category called “operational excellence” in the hopes of inspiring even more employees to get involved. The challenge? To come up with ways to be more productive and efficient.</p><p>“I was a little worried that not as many people would participate because the theme wasn’t as tech-driven,” Jing says. “But everyone loved it. In the end, operational excellence had the most project submissions ever. It was a great experience for me to see the excitement around a common vision and goal. The hackathon helps BlackRock advance its technology and have a stronger employee community.”</p><p>Sometimes, HACK:BLK ideas do lead to real products, with engineering leaders adding them to their roadmaps. Mimi recalls a past hackathon project that was implemented within Aladdin. “The team didn’t come in first place, but their idea is now a real business,” she says. “A lot of times, it’s not a flashy project you see in the headlines.”</p><p>Take Smart Sensei. According to Mael, “Our use case caught the eyes of an AI team in London, and I’m going to be meeting with somebody about collaborating on it. Hopefully by the end of the year, we’ll have some beta users testing it.”</p><p>Before participating in HACK:BLK, Mael never imagined he’d have the chance to pursue his idea. “I was surprised at first that my manager pushed me to go and do it,” he says.</p><p>And now, he knows better. “There is a mindset at BlackRock that you should be like a student — always learning and improving,” he says. “The hackathon is quite representative of the company, and how we always try to find new ways to do things.”</p><p>A version of this blog was originally published on <a href="https://www.themuse.com/advice/blackrock-hackathon-innovation">The Muse</a> &amp; the <a href="https://careers.blackrock.com/2023/11/13/category1/how-blackrocks-hackathon-empowers-employees-to-be-innovative/">BlackRock Careers site</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8ba3fb85a848" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/how-blackrocks-hackathon-empowers-employees-to-be-innovative-8ba3fb85a848">How BlackRock’s hackathon empowers employees to be innovative</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Similarity Learning, the art of identifying neighbors]]></title>
            <link>https://engineering.blackrock.com/similarity-learning-the-art-of-identifying-neighbors-a74c975e6afc?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/a74c975e6afc</guid>
            <category><![CDATA[similarity-learning]]></category>
            <category><![CDATA[quant]]></category>
            <category><![CDATA[data-scientist]]></category>
            <category><![CDATA[similarity]]></category>
            <category><![CDATA[data-science]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Thu, 16 Nov 2023 17:44:28 GMT</pubDate>
            <atom:updated>2023-12-04T23:35:17.273Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/640/1*XZKja5gbUxyEGe0CGdmfKQ.jpeg" /></figure><h4>BlackRock data scientists discuss how they think about similarity and advanced risk and portfolio management processes</h4><blockquote><em>By: </em>Members of the<strong> Aladdin Financial Engineering (AFE) </strong>team at BlackRock</blockquote><blockquote><strong><em>Stefano Pasquali</em></strong><em>, Managing Director &amp; Head of AFE Investment AI Modeling &amp; Research, </em><strong><em>Philip Sommer</em></strong><em>, Senior Director of Product, Head of AFE Liquidity and Trading Analytics, </em><strong><em>Dhagash Mehta, </em></strong><em>Senior Principal Data Scientist</em><strong><em>, </em></strong><em>Leads Core AI Research within AFE and </em><strong><em>Dhruv Desai, </em></strong><em>Data Scientist III in AFE Investment AI Modeling &amp; Research</em></blockquote><p>What is Similarity Learning? According to search engines similarity is a relation between objects that constitutes how much these objects are alike. There may also be found many synonyms of similarity such as resemblance, comparability, likeness, correspondence, etc.</p><p>In our day-to-day routine we frequently leverage the concept of similarity. For example, while searching for a product on e-commerce sites, we see products which are relevant (“similar”) to our target search. This idea can be easily extended to other use cases, like finding a house to buy, recommending friends on a social network, or identifying top results on a search engine. In each of these examples we are trying to identify neighbors for a given object or a group of objects or filters or a query.</p><p>At BlackRock, within the Aladdin Financial Engineering team, data scientists are a diverse group of individuals, that each bring unique skills and experiences from various quantitative fields like computer science, mathematics, theoretical physics, optimization, trading, and investment research. Collectively, our team is responsible for delivering analytical solutions specifically from an applied machine learning point of view. Using our diverse background we have analyzed multiple aspects of trading and liquidity for bonds, equities and other asset classes including mutual funds and ETFs, from similarity learning point of view. We settled on framing similarity as a mathematical concept, i.e., similarity measures how far or close two objects are in some chosen space of variables. Then, our task to correctly quantify the similarity becomes to correctly quantify the distance between objects in this space.</p><p>Similarity has many applications in the world of finance, as well. For example, identifying “liquid substitutes” in <a href="https://www.pm-research.com/content/iijinvest/32/1/104">trading and investment processes</a> helps to efficiently source liquidity and significantly improve fill rates, as well as reduce transaction costs, while also reducing the negotiation cycles between traders and portfolio managers. This is a crucial application of bond similarity. Another one is discovering fair pricing for illiquid securities with little to no observable data on the target bond. Here we need to rely on price movements of “similar” securities. In general, similarity between every financial object can help identify alternative, and possibly more efficient, investments at every level of granularity.</p><p>Over many years of doing systematic research, our team of data scientists have shown that, instead of manually imposing a distance metric such as the Euclidean distance to determine similarity between two objects, we can get significantly better results incorporating more advanced techniques. We can use machine learning (ML) to learn the appropriate distance metric for the given dataset! More specifically, we have focused on tree-based methods to learn similarity in a supervised fashion.</p><p>These methods include decision trees, random forests, and gradient boosting trees to learn the distance metric. A method using random forests, for example, was published by our team in this <a href="https://arxiv.org/pdf/2207.04368">article</a> and has since been expanded other tree-based methods with appropriate algorithm-specific adjustments.</p><p>We have been able to develop advanced trading analytics using similarity learning as well as using other ML and quantitative techniques, so that portfolio managers can add considerable value to their clients’ portfolios across both index and active strategies. This directly improves trading processes by replacing heuristics-based sector categories broadly used in portfolio management and risk factors with dynamic, data-driven cohorts of similar instruments. In turn, help identify liquid substitutes, improving order fills and order matching. See our related papers here: <a href="https://arxiv.org/abs/2207.04368">article-1</a>, <a href="https://arxiv.org/abs/2207.04959">article-2</a>, <a href="https://arxiv.org/abs/2308.06882">article-3</a>, <a href="https://arxiv.org/abs/2308.08031">article-4</a>, <a href="https://arxiv.org/abs/2207.07183">article-5</a>, <a href="https://www.pm-research.com/content/iijinvest/32/1/104">article-6</a></p><p>These new advances in data science are providing interesting ways to advance risk and portfolio management processes, and this is just the beginning!</p><p>Learn more about <a href="https://careers.blackrock.com/life-at-blackrock-2/technology/">technology careers at BlackRock</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a74c975e6afc" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/similarity-learning-the-art-of-identifying-neighbors-a74c975e6afc">Similarity Learning, the art of identifying neighbors</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Cranking the Voltage on Spark: Achieve Peak Performance with Optimization]]></title>
            <link>https://engineering.blackrock.com/cranking-the-voltage-on-spark-achieve-peak-performance-with-optimization-24da87c44ae3?source=rss----e8ae0174b0d8---4</link>
            <guid isPermaLink="false">https://medium.com/p/24da87c44ae3</guid>
            <category><![CDATA[scale]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[apache-spark]]></category>
            <category><![CDATA[spark]]></category>
            <category><![CDATA[apache]]></category>
            <dc:creator><![CDATA[BlackRockEngineering]]></dc:creator>
            <pubDate>Wed, 11 Oct 2023 22:00:46 GMT</pubDate>
            <atom:updated>2023-11-08T20:27:12.280Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Yd8bh1eFUM7hTl6PP0NjFQ.jpeg" /></figure><h4>Soumyata and Mohi describe their use of Apache Spark framework to generate sophisticated risk analytics for millions of portfolios in minutes. They deep dive into Spark optimizations and share some tricks to increase the efficiency of Spark applications.</h4><blockquote><em>By:</em><strong><em> Soumyata Binani,</em></strong><em> Engineer I, &amp;</em><strong><em> Mohi Mohi, </em></strong><em>Engineer I in Aladdin Wealth Tech</em><strong><em> </em></strong><em>Distributed Compute team, BlackRock</em></blockquote><p>Whether you’re a seasoned Spark aficionado or just embarking on your journey, delving into the nuances of Spark optimization can make a notable difference in the pace, effectiveness, and scalability of your big data processing pipelines.</p><p>Here at BlackRock in <a href="https://www.blackrock.com/aladdin/products/aladdin-wealth">Aladdin Wealth</a>, we manage to generate sophisticated risk and portfolio analytics for millions of portfolios in less than 30 minutes. Curious to know how? Well, the secret lies in our use of the Apache Spark framework. So, let’s take a plunge into the nitty-gritty and discover some of the tricks up our sleeves that help us squeeze out the maximum juice of efficiency from our Spark applications!</p><h3>Taming the Power Duo: Driver and Executors</h3><p>Imagine your Spark application as a team, with two key players: the driver and the executor. The driver leads the game, dividing and assigning tasks to the executors. The executors (the dedicated task doers) work in parallel, utilizing CPU cores and memory effectively to complete the tasks together. Configuring this duo is as much an art as a science. While drivers are straight-forward and can run with a minimal set of hardware, determining the ideal executor configuration for a Spark job is a challenging task.</p><h4>“How should one choose between plump vs lean executors, i.e., a few large executors or many small executors?”</h4><p>For instance, we are submitting a job to a cluster that has a total of 150 GB of memory and 20 cores; do we create five 30 GB four-core executors, or ten 15 GB two-core executors? While there is no one-size-fits-all answer, understanding the implications of each approach in terms of CPU and memory can guide our decision-making process.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kDAPIZ_n03ocJG6Z9YL4PQ.png" /></figure><h4>Lean, Mean, Task-eating Machines?</h4><p>Even though using lean executors may seem advantageous in terms of resource utilization, it comes with two potential downsides:</p><ol><li>Running out of resources to compute a partition can lead to memory issues and disk spilling during shuffling or when dealing with unbalanced data.</li><li>Having an excess of executors might not use resources effectively because of communication overhead. Each executor brings some overhead with it, and if they’re too small, a big part of their space gets used for overhead instead of actual work. For instance, having lots of 1 GB executors leads to about 25 percent of each one’s space being taken up by overhead.</li></ol><h4>…or Big Data Hoggers?</h4><p>Employing plump executors can also be wasteful. Placing very large executors on nodes (VMs) may result in unused space (memory or cores) on them. Additionally, it may cause delays in garbage collection and poor performance if there are too many cores per executor.</p><p>Ultimately, the optimal choice depends on factors like memory constraints, partition computation, communication overhead, and performance considerations, and a careful evaluation of resources necessary for configuring an application in a Spark cluster.</p><h3>Better Distribution == Better Performance</h3><p>Spark takes a relaxed approach while transforming an <a href="https://sparkbyexamples.com/spark/spark-rdd-vs-dataframe-vs-dataset/">RDD/Dataframe</a>, waiting until you trigger an action for the result. When launching a Spark application, the driver program converts it into a single job. A job consists of stages, representing unique data operations that can run simultaneously or sequentially. Some stages run concurrently while others wait. Stages comprise tasks that can be executed in parallel. To handle large data volumes efficiently, Spark divides the input dataset into smaller partitions. These partitions are then distributed across multiple executors, where tasks process their assigned data diligently.</p><h4>Divide and Conquer</h4><p>Effectively dividing your data into partitions allows your resources to conquer the workload! While Spark automatically breaks the data into partitions during loading, the repartition() method can be utilized to re-partition the data according to the specific requirements of your application. Beware, using repartition() method may cause data to be shuffled between the executors to create the desired number of partitions.</p><p>Suppose you have at your disposal 20 cores and 100GB of memory to work with, and your task is to process 10,000 records. Now, let’s say you have configured four executors, each with five cores and 25GB of memory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XfTxWZM00XyC9T5qKO6qJg.png" /></figure><p>In this example, the input file is split into 20 partitions, and since we have 20 cores available, all partitions are processed simultaneously in parallel. Here we see a 1:1 mapping between partitions and cores but typically, the number of partitions should be a multiple of the total cores for optimal performance. If the executors encounter memory constraints, increasing the number of partitions can help spread out the memory footprint. For instance, we could have used 40, 80, or even 200 partitions if needed.</p><p>In a case where you are certain that you must reduce the number of partitions, you can utilize a nifty little method called coalesce(). This will help repartition the data without shuffling data between executors.</p><h4>Skewness — thief of our perfectly distributed world</h4><p>The previous example may have seemed overly idealistic because in practice, data records exhibit variations in size and, consequently, differences in computation times are observed. In Spark terminology, this phenomenon is known as “data skewness”, which can result in delays and resource overload.</p><p>One way to analyze if this is happening in your application is by checking stage summary metrics, which can be accessed by going to <strong><em>yarn-ui -&gt; Stages tab -&gt; Select a stage</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/902/1*P8tj7h5aA9POdnvgiUuNyw.png" /></figure><p>The above case, albeit an extreme one, illustrates the impact of skewed tasks. The entire stage experiences a bottleneck due to the task that takes the maximum time, causing certain executors to remain idle for an extended period while waiting for the largest partition to finish processing on a specific executor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*K0Fn-xX9sPqLghEQKfo_Qw.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9WlmizIgarKDkrvG9W-e7A.png" /></figure><p>The above graphs (explained in detail in a later section), show the heap utilization (consequently CPU utilization) of two executors during a run. One executor is constantly active while the other remains idle, waiting for the first executor to complete processing.</p><p>One easy way to fix this situation is by using lean executors, which should mitigate the impact of this skewness. However, lean executors may not be applicable in all cases. In all such situations, an alternative approach called “<a href="https://jasonrbriggs.com/journal/2022/12/17/salting-with-spark.html">salting</a>” can be employed. Salting involves breaking down the largest partitions into smaller chunks, thereby reducing data skewness.</p><h3>Memory Tuning</h3><p>After determining the appropriate memory allocation to ensure that your executors do not run OOM, the next step is to focus on optimizing garbage collection (GC).</p><p>Before you start fine-tuning GC, you will first need to obtain the GC logs. To capture these logs, simply include the following in your executor’s JVM args when executing the spark-submit command:</p><p>-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps</p><p>The GC logs can be retrieved from the stdout of an executor, which can be found on <strong><em>yarn-ui -&gt; Executors tab</em></strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UeENkYrk_kvwTVjpsu3T6g.png" /></figure><h4>Analyzing GC</h4><p>When analyzing GC results, there are two key aspects that require attention:</p><ul><li><strong>Throughput:</strong> A higher percentage indicates that your CPU cores are spending more time on actual computation rather than collecting garbage. It is desirable to aim for a percentage between 95% to 98%. Additionally, you can explore the graphical and tabular data provided by the tool to assess the distribution of GC pauses (both major and minor).</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5K6q21xzwPmiXCYGgWEZOw.png" /></figure><ul><li><strong>Heap Utilization:</strong> Examining heap utilization is essential, as we typically end up over-provisioning the heap during the early set–up stage. To determine the ideal heap size, run your application with the highest expected load and retrieve the GC logs from the executor with the highest write size (if the application does not write anything, select the executor with the longest task time). Use this to identify the peak heap usage. Add a 10–20% buffer on top of this value, which will give you the recommended heap size. Keep in mind that Spark provides a 10% memory overhead, so consider this when selecting the executor memory size.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aFUEXioBL5jYrgWQsFSUMQ.png" /></figure><h3>Handling Data Exchange</h3><p>You might raise a question, <em>“What if a task running on an executor needs to access data from a different partition that is running on another executor?”</em></p><p>To support this, Spark internally uses a process known as “shuffling”, which involves redistribution and exchange of data between partitions across the cluster, requiring data to be transferred over the network. However, shuffling can be resource-intensive and time-consuming due to the movement of data across the cluster. Therefore, minimizing shuffling is essential for optimizing your Spark jobs. Techniques like data partitioning, broadcast variables, and leveraging efficient operations like map-side aggregations can help reduce shuffling.</p><h4>Sharing != Caring</h4><p>Contrary to the conventional belief that ‘sharing is caring’, in the context of the Spark world, it proves more advantageous for each executor to prioritize self-interest and avoid sharing data with others. This approach stems from the fact that sharing data over the network can introduce unwanted delays. Let’s explore some powerful techniques that enable us to minimize data shuffling and turbocharge our I/O operations:</p><ul><li><strong>Column Pruning: </strong>Imagine picking juicy fruits from a tree. You wouldn’t waste your time plucking unripe or unnecessary ones, right? Similarly, in Spark, selecting only the vital columns early on in your dataframe transformations saves us from processing needless data. Additionally, we can further optimize by applying filters or predicates as early as possible in our transformations. <br>Let’s say you have a Dataframe called ‘employeeData’ with columns id, name, age, department, and salary and you want to perform some transformations and calculations but you’re only interested in the name and salary columns.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/786/1*Y9loA9lRdiTYdsl5zGWS3A.png" /></figure><ul><li>In this example, the select() method is used on the ‘employeeData’ DataFrame to specify the columns - name and salary. This operation prunes the unnecessary columns from the DataFrame and returns a new DataFrame called ‘prunedData’. Then a filter is applied on it to extract names starting with “a”. This targeted approach reduces the data size and improves processing speed resulting in improved application performance.</li><li><strong>Broadcast Variables:</strong> Picture this: instead of sending individual messages to every worker, why not deliver a message once to all of them simultaneously? Broadcasting variables does exactly that! By sharing essential variables with all workers in one go, you eliminate redundant data transfers. This means less time wasted shuffling data and more time spent on actual computation. However, be careful of broadcasting large and complex variables! They are stored in each executor’s memory only as a serialized form, and their deserialized heap representations are not shared across tasks. Therefore, the deserialization cost is paid on every task.</li><li><strong>Caching and Persistence:</strong> Ever wished you could avoid doing the same task over and over again? Caching intermediate results or frequently accessed datasets in memory or on disk does just that. It’s like having a shortcut to skip redundant computation and I/O operations. <br>Caching data in memory creates a fast-access storage for Spark, allowing swift reuse of prior computations, ultimately saving time and resources. Memory caching is particularly effective when your datasets fit comfortably within the available memory space, ensuring rapid access without the need for costly disk operations.<br>Alternatively, when datasets are too big for memory, caching to disk is beneficial. While disk caching might not provide the same lightning-fast access as memory caching, it’s still significantly faster than recalculating the data from scratch. It involves writing the data to disk in a serialized format, which reduces the storage overhead compared to caching the entire object in memory.</li></ul><h3>Code Optimization</h3><p>While adjusting Spark parameters plays a vital role in optimizing performance metrics, it is equally important to focus on code optimization. Relying solely on parameter tweaks can only take us so far. Code optimization revolves around minimizing unnecessary computations, reducing data transfers, and maximizing resource utilization. Streamlining our code empowers us to achieve significant efficiency gains, enabling us to push the boundaries of performance even further.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/904/1*ts0rNeMcrO61RUkasuH-jw.png" /></figure><p>Despite Spark’s inherent ability to optimize code and generate an optimized execution plan, comprehending the intricacies of your code can still present a challenge. However, you can gain valuable insights into the code flow by examining the Directed Acyclic Graph (DAG) of stages/tasks displayed on the YARN UI. Note that DAGs of dataframes are usually more complicated as Spark utilizes the Catalyst Optimizer to further optimize its execution plan. This can be analyzed in detail using thedf.explain() method.</p><h4>Method Matters</h4><p>It is common for us to fall into the trap of utilizing the first method that solves our problems, often neglecting to consider how these methods provide the desired functionalities. For instance:</p><ul><li><strong>flatMap() vs </strong><strong>map() </strong><strong>+ flatten():</strong> While both approaches yield the same result, they differ significantly in their execution. flatMap() avoids the creation of intermediate collections for each element, leading to reduced memory usage and potentially faster execution, especially when dealing with large datasets. However, this approach limits the flexibility to perform operations between the mapping and flattening steps.</li><li><strong>reduceByKey() vs </strong><strong>groupByKey(): </strong>Using reduceByKey() can reduce unnecessary data shuffle required while reducing a key-value pair-based data.</li><li><strong>mapPartition() vs </strong><strong>map(): </strong>Both provide you a transformation capability but differ in one key aspect: mapPartition() runs your transformation on one partition while map() runs your transformation on each row. This could be helpful if you have heavy classes being initialized while transformation is happening.</li><li><strong>take() vs </strong><strong>collect():</strong> Calling collect() is an easy way to run out of memory on your driver. If not needed, always resort to take(), which will get you the first ‘n’ records it encounters.</li></ul><p>By considering and exploring various methods, we can make informed choices that optimize memory usage, execution speed, and overall performance.</p><h3>Wrap Up!</h3><p>From understanding the quirks of your data to utilizing Spark’s awesome features, it’s all about maximizing performance and minimizing bottlenecks. Think of it as being the pit crew for your data race car, constantly monitoring and tweaking to reach victory lane. So, gear up and plunge into the realm of Spark optimization, where you can transform your sluggish pickup truck into a Formula 1 racecar!</p><p>Learn more about <a href="https://careers.blackrock.com/life-at-blackrock-2/technology/">technology careers at BlackRock</a>.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=24da87c44ae3" width="1" height="1" alt=""><hr><p><a href="https://engineering.blackrock.com/cranking-the-voltage-on-spark-achieve-peak-performance-with-optimization-24da87c44ae3">Cranking the Voltage on Spark: Achieve Peak Performance with Optimization</a> was originally published in <a href="https://engineering.blackrock.com">BlackRock Engineering</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>