A customer working on a VIP component identified that the performance of one of their protocol checkers, written in ‘e’, is significantly worse than the performance of the competing solutions. Profiler reports of a representative test case pointed to a few complex methods, which consumed about 90% of the time. What stood out in these methods was the use of the pop0() pseudo-method on a couple of list buffers. This was a strong evidence that these lists are used as FIFO queues. SN support together with R&D suggested to try replacing these list buffers with a deque from the newe Template Library (eTL)—a FIFO data structure from the new open source package.
Here are the steps that were required:
- Download eTL, copy only deque code under different name and package to avoid any possible name clashes
- Change definition from list of bit to deque of bit
- Take care that the deque is initialized with “new”
- Grep and replace index access with set/get method
- Iteratively try to load code to identify and fix incompatible pieces of code, and all the fixes were trivial
- Run a testcase to find if behavior is okay, which revealed a couple of non-obvious compatibility problems—with a trivial fix immediately as they were identified
The whole process took about four hours, where most of the time was spent on debugging non-obvious problems, because the engineer who did that was not familiar with the code. The immediate result was that the performance of the test case was improved by 2X. Identifying and replacing additional FIFO lists improved performance even more, including other flows of the VIP component.
Most of the obviously incompatible pieces of code were operations on whole lists, such as <fifo>.add(<list>) or <list>.add(<fifo>). There were two non-obvious cases of incompatibility:
- Unlike the size() pseudo-method of list, which returns int, size() method of eTL deque returns uint
- In the fifo.add(pack(…)) expression, when fifo is a list of bit, the add() pseudo-method can accept bit as well as list of bit, and pack expression, whose return type depends on the context of the expression, returns list of bit. However, add() of deque of bit expects bit, so only one bit is added, and this produces different results
e Template Library
The eTL is a collection of generic data structures and related operations, intended to overcome limitations of native e lists. Right now, it includes several containers: vector, deque, linked list, keyed set, keyed multi set, and interators for them. There are plans to add more data structures and improve usability. The library is 100% open source and 100% legal e code. You are free not only to use it as is, or as a base type for your own using ‘extend’, but also to change it so that it fits your needs better, or create your own data structures using ideas and code sniplets from eTL. Code and documentation of eTL can be downloaded from http://github.com/etl-spmn/etl.
As the name suggests, the eTL uses e templates, so the data structures can be parameterized with most e types like numeric, enum, structs, and strings (however there are some limitations on special types like lists, sets, etc.).
All the documentation is embedded in comments, and there is an edoc that collects them.
The eTL containers are non-generatable structs that encapsulate different data structure behavior using a unified API, which was intentionally made very similar to the API of e lists: many methods of eTL containers have the same or similar name and signature and abstract behavior as list pseudo-methods. This allows you to experiment replacing lists in existing code with minor effort. The performance of the API methods may differ for different containers, and that is exactly why we need them. However, to benefit from their special properties, you might need to make changes that are more significant.
- The simplest eTL container is a vector, which is effectively a wrapper for regular list. Still it can be useful when you need hooks or checks to specific operations (add/pop/remove/access), or when you can save memory by having NULL vector instead of empty list, etc.
- Probably the most useful container is deque: it implements circular buffer using a regular list. It is intended for FIFO buffers and has significantly better performance of add0/pop0 operations, especially when it contains large number of items. The performance of these operations is always O(1) while for regular list it’s O(n). The price you pay for this improvement is a little overhead of all other operations.
- linked_list is also good for FIFO buffers, and in addition it also has O(1) insert and delete operations at an arbitrary index. However to use it, an iterator is required. In addition, access by index is O(n), so special caution is needed.
- keyed_set, similarly to vector, is mostly a wrapper for “list (key:it)”, but it also enforces uniqueness of items, preventing undefined behavior.
- keyed_multi_set allows deterministic behavior when there are several equal items: the last inserted is the first found, and if it is removed, the one inserted just before it will be found. It also allows access to multiple items related to the same key, if exist.
Each of the containers has its own iterator, however all iterators have exactly the same API, so the code that uses iterators can handle any container. The linked_list iterator is required to take advantage of its fast operations—when the iterator is in a position inside the linked list, it can add or remove neighboring nodes in constant time.
In coming updates, we plan to add maps, more methods and macros for containers, and are open for any other ideas as well.
Rodion Melnikov
e Language team, Specman R&D