Advent of Code is back! Unwrap daily challenges to sharpen your Alteryx skills and earn badges along the way! Learn more now.

Engine Works

Under the hood of Alteryx: tips, tricks and how-tos.
AdamR_AYX
Alteryx Alumni (Retired)

In my last blog post (Part 1 - Why AMP?) I looked at the reasons why we built a new multi-threaded core engine; in this post we will take a look at some key concepts that make the AMP engine tick. These are not things that you need to know about to use the engine, the engine takes care of it all for you, but more for the technically advanced user who is looking for an understanding of what’s going on under the hood. We will cover the following concepts:

  • Record Packets
  • The Memory Allocator
  • The Task Scheduler

 

Record Packets

 

In part 1 of this series we talked about the overhead of making an application multi-threaded and how if we did that on a per row basis then the cost of multi-threading would outweigh the benefits and although we could use more CPU power we would ultimately be slower. The way we tackled this issue in AMP is that tools no longer process data on a record by record basis.

Instead, they process data in record packets.

A record packet is a fixed size allocation of memory (today 4Mbs) which contains a number of records. So if a record is 100k a packet can hold about 4000 records. Tools multi-thread work on a per packet basis which means the cost of multi-threading is spread over all the records in that packet and no longer has a detrimental effect on overall runtimes.

A record packet is always fixed in size and we try to keep packets relatively full to help with performance (Having packets only marginally filled makes for a very inefficient use of memory). Larger fields (think big spatial data or string fields) are held outside the packet. We will cover these in a future post, just know for now that you don’t need to worry about large fields taking up all the space in a packet.

 

Memory Allocator

 

Next, to deal with all these record packet memory allocations we have a new component called the Memory Allocator. The allocator’s job is to “allocate” memory for record packets and other large data fields and to manage storage of that memory.

The allocator will ensure that if you have more data than you have set for AMP to use it will first compress the record packets and then write them to disk to keep memory usage under the set limit.

This means that an individual tool does not need to worry about where that memory is stored, it receives a handle to a memory packet and when it wants to read data from that packet, the memory allocator will ensure that data is ready and available in RAM to read and write from.

 

Task Scheduler

 

The e1 engine would push records between tools in the main thread of the application, this aspect of the e1 architecture is ultimately what prevents it from being able to effectively use all of your cores on your machine. There are background threads in individual tools which do other work, but importantly only one record can be moving between tools at any given time.

 

AdamR_0-1596015708102.png

 

In AMP the actual work of a workflow is co-ordinated by a new component called the Task Scheduler. Tools will produce “tasks” of work, typically on a single packet of data and the scheduler will pull tasks out of a queue and efficiently schedule them across however many cores the user has set up for use by AMP. This is the heart of why AMP can make use of so many cores because a given tool can have multiple tasks all running together in parallel.

 

AdamR_1-1596015708102.png

 

Having introduced some foundational concepts of AMP, in our next post in this series we will take a look how we do summarize and join in a massively parallel way.

Adam Riley
-

Former account of @AdamR. Find me at https://community.alteryx.com/t5/user/viewprofilepage/user-id/120 and https://www.linkedin.com/in/adriley/

Former account of @AdamR. Find me at https://community.alteryx.com/t5/user/viewprofilepage/user-id/120 and https://www.linkedin.com/in/adriley/

Comments
JZZChew
6 - Meteoroid

Does the AMP Engine parse characters as UTF-8? The reason for asking is that I noticed that Dynamic Input throws a peculiar error "Invalid char in UTF-8 string" with AMP Engine turned on but not so when it's off, when reading in something like a non-breaking space (UTF8 U+00A0, 194 160).

AdamR_AYX
Alteryx Alumni (Retired)

@JZZChew It stores all of its text data internally as UTF-8 but it should parse text the same as the original engine. Are you able to send us an example workflow so we can take a look at what is going on?

JZZChew
6 - Meteoroid

This was with a dynamic input to an Azure SQL Database (service, not VM) via an alias (aka). I tried to see if I could replicate the error with offline files or even an Access database but it looks like those data sources do not throw this error.

For what it's worth, here's a link to the workflow but it will need another (online) SQL Server to connect to under the "db" alias.

Example workflow 

Dynamomo
11 - Bolide

Hi @AdamR_AYX!  Hope all is well with you!

I've enjoyed these and the podcast as well.  Are there more articles in this series or are they still in the works?

Maureen

BGirkins
8 - Asteroid

What is the reason AMP now requires Double fields to only be JOINed to other Double fields? On the original engine you could do, for example, a JOIN with a Int64 and Double.

AdamR_AYX
Alteryx Alumni (Retired)

"What is the reason AMP now requires Double fields to only be JOINed to other Double fields? On the original engine you could do, for example, a JOIN with a Int64 and Double."

 

It because in AMP we use a different algorithm to do the join.

 

In the original engine we would sort both incoming data streams by the join fields and then take records off the top of each sorted set of records and compare the join fields for equality.

 

In AMP we use a hashing algorithm to bucket the incoming records into multiple sets and then perform the join in multiple threads by splitting hash buckets across different threads. The problem comes in that a hash of a number stored as an Int64 is completely different to the hash of that same number stored as a double. So those 2 records would end up in different has buckets, being processed in different threads and not successfully join.

 

Also in general joining on floating point numbers is not recommended due to issues with comparing floating point numbers.