SPO600: SIMD Lab (Lab 5)

In this lab we will be working on AArch64 system.

Part 1: Auto-Vectorization

  • For this lab, we are using the same vol1.c file as in the Algorithm Selection Lab. For the first requirement we had to modify the Makefile to include -fopt-info-vec-all. After compilation, this option generates a list of loops that have been vectorized as well as non-vectorized along with reasons for the same.
 CCOPTS = -g -O3 -fopt-info-vec-all
  • We compiled vol1.c and the output shows us that loop 1 and 3 are not vectorized. Loop 2 is vectorized.
Analyzing loop at vol1.c:38
vol1.c:38:2: note: not vectorized: unsupported use in stmt.


Analyzing loop at vol1.c:32
vol1.c:32:2: note: LOOP VECTORIZED

Analyzing loop at vol1.c:25
vol1.c:25:2: note: not vectorized: loop contains function calls or data references that cannot be analyzed
  • Our next goal is to modify one more loop (1 or 3) so that it becomes vectorized. Initially this is what our last non-vectorized loop looked like
// Sum up the data
for (x = 0; x < SAMPLES; x++) {
ttl=(ttl+data[x])%1000;
}

‘data’ is summed up and added to ‘ttl’ and then its remainder. This loop was not being vectorized because of the modular that calculates the remainder. We slightly changed the code to get the remainder after the loop

 // Sum up the data
for (x = 0; x < SAMPLES; x++) {
ttl+=data[x];
}
ttl=ttl%1000; 

After compiling again, the loop is now vectorized.

Part 2: Inline Assembler

  • We looked into add.c and understood its functioning. The code adds ‘a’ and ‘b’ and assigns it to ‘c’
int main() {
int a = 3;
int b = 19;
int c;
// __asm__("assembly code template" : inputs : outputs : clobbers)
__asm__("add %0, %1, %2" : "=r"(c) : "r"(a),"r"(b) );
printf("%d\n", c);
}

Our next step was to modify the code so that does ‘b%a’ instead of the addition using inline assembler. I used ‘udiv’ to find the result of ‘b/a’. ‘udiv r0, r1, r2’ divides ‘r1’ and ‘r2’ and places the quotient in ‘r0’. The remainder should calculated separately. For the remainder I used msub. ‘msub r0, r1, r2, r3’ loads ‘r0’ with ‘r3-(r1*r2)’ which is the remainder.

// __asm__("assembly code template" : inputs : outputs : clobbers)
__asm__("udiv %0, %1, %2" : "=r"(c) : "r"(b),"r"(a));
 __asm__("msub %0, %1, %2,  %3" : "=r"(c) : "r"(a),"r"(c), "r"(b)); 
printf("%d\n", c);
}
  • In the next part, we had examine vol_inline.c. It contains a volume scaling program which uses inline assembler and SQDMULH. This is the result with 5000000 SAMPLES (this is the value of SAMPLES in vol.h)
  • When using 500000000 SAMPLES, here is the result
  • when reducing it to 50 SAMPLES,

The run time and the results of the program depends on the value of SAMPLES. Increasing the value of SAMPLES results in a greater run time.

Part 3: C Intrinsics

  • In this part, we have to examine vol_intrinsics.c which uses C intrinsics for a volume scaling problem to access AArch64 SIMD instructions. As I did earlier I ran the program with initial 5000000 SAMPLES
  • When increasing it to 10000 SAMPLES, here is the result
  • when reducing it to 10 SAMPLES, this is the result

Once again, the run time and the results of the program depends on the value of SAMPLES. Increasing the value of SAMPLES results in a greater run time.

SPO600: Benchmarking and Profiling

After encrypting the partition, I tried to benchmark its performance with the time command. Running the time command as a root user here is the result I get,

time sudo cryptsetup

The user CPU time (time spend outside the kernel) or the time spend within the process is very small indicating that the actual execution time of the program itself is really small.

I tried using the perf profiler to see the sampling results.

perf record cryptsetup
perf report

Here’s what the result looked like.

We see that most of the shared objects are dynamic linkers (ld) or other libraries (libc- standard c libraries). There are also two instances when the samples were collected when the program was running at the kernel level.

I also tried to analyze the results with the gprof profiler. While running the configure command I used -pg and -O2. I ran the gprof with the cryptsetup binary and here is the result:

Initially this graph made no sense to me because it looked nothing like the ones I usually see. But upon more research I was able to understand why the graph looked weird. Since cryptsetup is only a command line tool that interacts with the dm-crypt module for creating, managing and accessing the encrypted devices, much of the work related to encryption and decryption is done by the kernel itself and cryptsetup almost plays no role in the actual encryption of the device. The below figure shows the interface between the user-space and the kernel.

Source: https://www.cyrill-gremaud.ch/encrypt-linux-partition-using-luks/

Cryptsetup is a user-space tool used by LUKS to configure dm-crypt to perform encryption. dm-crypt is a part of the crypto API (cryptographic framework) in the linux kernel and helps crypt the target and provide transparent encryption of block devices.

Figure 1: cryptsetup (top) prompts the user for a password and uses a hash to create a fixed length key, which it then passes on to the kernel (center). DM-Crypt (bottom) uses the key to encrypt and decrypt data on the hard disk (or backing block device).
Source: https://nnc3.com/mags/LM10/Magazine/Archive/2005/61/065-071_encrypt/article.html
Figure2: cryptsetup-LUKS stores the parameters for the encrypted partition in the backing block device partition header (top left). The derived key protects the master key, which encrypts the data on the partition. Source: https://nnc3.com/mags/LM10/Magazine/Archive/2005/61/065-071_encrypt/article.html

Cryptsetup generates a cryptographic key from the passphrase and passes it to the kernel. When the user supplies a password, cryptsetup uses a hash algorithm to compress the information to a fixed number of bytes (Figure 1). The encryption process requires two parameters- the algorithm and the mode along with the derived key is passed down to the kernel. From there, the dm-crypt along with crypto-API handles the encryption. There is a slight difference in cryptsetup-LUKS in that it introduces an additional password management layer. LUKS password management involves key hierarchies, PBKDF2 and anti-forensic information storage. The key hierarchy is responsible for inserting an additional encryption layer between the derived key and the key used to protect the data on the partition. The derived key protects the master key, which encrypts the data (Figure 2).

So in the entire process, cryptsetup does very little in terms of actual encryption but instead it helps the user as a command tool to help them access the partition with a password. This justifies the really small user-time it took while benchmarking its performance as well as the results produced by the profilers. This is why optimizing cryptsetup is a challenge.

The passphrase that we are asked when trying to first format LUKS device is used to create and encrypt a key that will be added to keyslot 0. (I had pasted a screen shot of this in my previous post). This passphrase is also hashed, stored and verified by LUKS when you open the device as discussed earlier.

A fast hashing algorithm should not be used as an attacker could easily crack the passphrase because they could test different combinations within shorter time. Hence there is no point of optimizing the hashing algorithm in terms of speed or suggesting a faster algorithm.

I came across the ‘cryptsetup benchmark’ command which benchmarks ciphers and KDFs( Key derivation functions -derive secret keys from a secret value such as a master key or passphrase), hashes and modes. So I decided to just look into the results and analyze them.

Applying the above discussion, the most secure hashing algorithm would probably be the slowest i.e. whirlpool. For the LUKS cipher, aes-xts-plain64 is considered to be the most secure. 128- bit key size can also provide a noticeable speedup. AES-NI is a cryptographic accelerator integrated into many processors. It helps speed up encryption and decryption and also protects against side-channel attacks.

What I learnt?

It is true that I could no optimize anything for my project because there wasn’t a scope for optimization. I had to first encrypt a partition before I could profile. But I learned a lot along the way. I chose a project that dealt with disk encryption- something I have never heard or used before. I learned how to encrypt a partition using LUKS, how the kernel conducts the encryption and decryption with the help of dm-crypt, the role of cryptsetup and so on. Though this wasn’t the success I aimed for, I still feel I have gained some knowledge trying work with this project.

SPO600: Encrypting data partitions using LUKS

Full disk encryption, LUKS and DmCrypt

Full disk encryption or whole drive encryption is used to encrypt your entire drive so that, say, a thief would not be able to access your personal data even if they walk away with your hard drive someday. Full disk encryption encrypts your data so that you can decrypt it only through a password or an encryption key. However, if your key is not strong enough, the thief will most likely crack it. LUKS (Linux Unified key setup) is used for Linux full disk encryption. Full disk encryption can protect your data only when it is locked.

DmCrypt is a transparent disk encryption subsystem in the Linux kernel and is a part of the device-mapper framework. Device-mapper maps a device to another (target). However, it cannot be used directly since it a part of the kernel. Cryptsetup communicates with the kernel to help us create and manage encrypted devices.

  1. Configuring LUKS partition

I had to encrypt a data partition to proceed further. My professor had arranged partitions on both the servers for me with which I could work on. You can find more information on how to create the partition here. I decided to first try encrypting the partition on x86 and then on Aarch64. /dev/mapper/fedora_localhost–live-lukstest is the partition on x86 and /dev/mapper/fedora00-lukstest is the partition on Aarch64. The command lsblk provides information about block devices in a tree-like format.

Here’s what I see when I used the command

ll  /dev/mapper/fedora00-lukstest 

a. crypsetup luksformat command is used for setting up the partition for encryption:

sudo cryptsetup luksFormat /dev/mapper/fedora00-lukstest

b. A logical device-mapper device is mounted to the partition. I used ‘backup2′ as the target in this case. This helps initialize the volume. The passphrase is requested and is not recoverable:

sudo cryptsetup luksOpen /dev/mapper/fedora00-lukstest backup2

Here’s what I see when I used the command

ls -l /dev/mapper/backup2

c. You can view the status of mapping with the following command:

sudo cryptsetup -v status backup2

d. cryptsetup luksDump helps check if the device is formatted for encryption:

sudo cryptsetup luksDump /dev/mapper/fedora00-lukstest

2. Format LUKS partition

a. The first step is to write zeros into the  /dev/mapper/backup2 device so that it will be seen as random data from outside i.e protecting against disclosure of usage patterns:

sudo dd if=/dev/zero of=/dev/mapper/backup2 

b. Creating a filesystem. I used the extf4 filesystem

 sudo mkfs.ext4 /dev/mapper/backup2 

c. Mounting the filesystem at /backup2

sudo mkdir /backup2
sudo mount /dev/mapper/backup2 /backup2
df -h
cd /backup2
ls -l

How to unmount and secure data?

To unmount and secure the partition the following commands are used.

umount /backup2
cryptsetup luksClose backup2

How to mount or remount encrypted partition?

Mounting is done with the following commands; where backup2 is the user given mapping name for the LUKS partition.

cryptsetup luksOpen /dev/mapper/fedora00-lukstest backup2
mount /dev/mapper/backup2 /backup2
df -H
mount

I followed the same procedure and encrypted the partition in x86. Here is the mapping status in x86.

In Aarch64, the keysize is 256 bits while in x86 it is 512 bits. Also, the key location is shown as different. The key location is keyring in x86 and dm-crypt in AArch64. There is also a difference in the size and the offset in both. You can find better explanation for each of these specifications in the dm-crypt documentation.

Release 0.4 : Coming to the end

Just like in release 0.3, release 0.4 also requires me to find an internal issue(Seneca’s telescope) and an external issue and send pull requests for them. For telescope, I decided to stick with testing side of it this time. My issue was to create a test for all the logger methods used and figure out if they really exist. This issue was created as a result of failure of the logger.debug function in one of the previous pull requests. I created a logger test file and added the tests in it. I followed the same example as used in facebook’s jest repository.

test('logger.methods to be functions', () => { expect(typeof logger.method1).toBe('function')});

Issue | PR

For my external pull request I decided to work with Microsoft’s STL. The issue is to revise a file so that it uses a consistent pattern for SFINAE: default template arguments with a specific form.

Issue | PR

Apparently, Microsoft’s STL is a slow progressing project and not much people seem to be interested in fixing their bugs. Honestly, I wish could have contributed more to telescope like others but because of the weight of other courses I was really constrained. Working on telescope was truly an amazing experience because for the first time I got to work with an open-source community in which I can actually meet the contributors and members because I see them every week. This made collaboration and discussion much easier. Telescope has been growing so rapidly that I really had to dig in to find my pull request and issue submitted last week. I’m actually happy for where telescope is right now compared to when we started. I truly enjoyed working with open-source this term.

SPO600: Project Selection and Building

Our final project for SPO600 involves selecting an open source project which contains a CPU-intensive task and trying to optimize the code in terms of memory usage, alter algorithm for efficiency or optimize an architecture-specific code, etc.

Stage 0 involves project selection. Honestly, this was one of the most difficult stage for me because we had to find a project where there is some opportunity to optimize. The difficulty was in building different projects because each project has different build instructions. I used the RPM(command line package management utility for Red Hat based systems ) to find a potential project.

rpm -qa |less   

Finally, I selected cryptsetup as my project and decided to stick on to that project itself. Cryptsetup is a disk encryption utility based on DMCrypt kernal module. I looked into the documentation to get a better understanding of this project as I have never worked with disk encryption before.

Building and installing the package:

Initially, I forked cryptsetup’s original repo and then cloned the fork locally to my X86 machine. A test branch ‘testb’ was created within the cryptsetup project folder so I could work on a test branch instead of the master. $ git checkout -b testb

Initially, I had to generate the configure script through configure.ac template. Autoconf is the autotool that helped me achieve this. This initial program contained Makefile.am which is processed by Automake placing the result in Makefile.in. $ automake –add-missing

I figured that the switch –add-missing adds a few links necessary for building the project such as depcomp and install.sh. However, I ran into errors. I found that my code contains autogen.sh script that automatically generates the configure script from configure.ac using autoconf and other files it may need (creates Makefile.am using automake).

I had to install many missing libraries before I could run configure. I ran the configure command with the -pg option( to use gprof profilier) added to the CFLAGS

./configure CFLAGS="-g -pg -O2" 

The makefile was modified to include -pg. I could finally run $ make -j 12. My next post will contain benchmarking results and scope for optimization.

Release 0.3: Completion

In my previous post, I had discussed briefly on the issues I was planning to work on for Release 0.3. I worked my internal issue (telescope) where I had to configure jest – a javascript testing framework so that it provides a coverage report for the tests. The coverage report would present an analysis of the number of statements, functions, branches, and lines of the code tested. I configured jest within the package.json by adding this piece of code:

 "jest": {
    "collectCoverage": true,
    "testEnvironment": "node",
    "coveragePathIgnorePatterns": [
      "/node_modules/"
    ]
  } 

collectCoverageindicates whether the coverage information should be collected. Our testEnvironmentis node andcoveragePathIgnorePatterns indicates that coverage information should be skipped for the file. This what ‘npm test’ would give us with this addition:

Thankfully, my code was merged.

issue | pull request

My external issue was to change virtual functions into overriders. This was an issue I found in Microsoft’s STL repository. I had to lookup virtual and override functions again. I picked to work on a single function – _Doraise() because the code was too big for me to alter every function. I had to identify base declarations of the functions and add override to the functions within the derived. The pull request I initially submitted did not pass all the tests because of spacing errors. Also, my commit message for the pull request was wrong. I struggled with git to refresh what I had earlier learned about squash, rebase and amend. But thankfully that worked too and I got positive feedbacks from the code maintainers.

issue | pull request

Release 0.3

We have come to the end of Hacktoberfest and I was successfully able to submit four pull requests this month. That is something I thought I could not accomplish and now I am proud of myself!

Our next task -Release 0.3 requires us to submit two pull requests- one internal (Seneca’s telescope repository) and one external. The requirement is that the internal PR should be merged even though the external might not be. The telescope is the project that would help us re-build the Seneca’s Planet (feed aggregator). Initially, I had faced some trouble with npm and node which delayed the process of finding an issue. But I found an issue filed by the professor which I think I can work on. The issue is to add a tool to generate a test coverage report for the code. The goal is to configure Jest so that it automatically generates this report. The internal issue I am working on can be found here. My external bug is an issue from Microsoft’s STL. The goal is to finish marking all virtual functions as an override. The external issue I am working on can be found here.

I think the hardest part of this release is going to be the internal part of it mainly because I think it is harder to find something to work on as well as you need to get your pull request merged. But let’s hope for the best and see how it goes!

SPO600: Algorithm Selection Lab (Lab 4)

In our previous lecture, we learned about the binary representation of data. In this lab, our main focus is on sound data. Digital sound is typically represented, uncompressed, as signed 16-bit integer signal samples. The volume of the sound can be changed by scaling the sample by a volume factor ranging from 0.00 (silence) to 1.00 (full volume).

The source code given to us during the lab:

  1. Creates 5,000,000 random “sound samples” in a data array.
  2. Scales those samples by the volume factor 0.75 and stores them back to the data array.
  3. Sums the output array and prints the sum.

The source code can be found below:

We build and test this file and noted that it produced the same output each time.

Result: 94

One of the command we used to determine the duration of execution is the time command:

time ./file_name

It prints the summary of the real-time, the user time and the system CPU time. ‘real’ time is the total or the elapsed or the wall clock time taken for execution. It is the difference in time from the moment you hit the Enter key until the moment the ‘wget’ command is completed. ‘user’ time gives us the amount of CPU time spent in user mode while the ‘sys’ time gives us the amount of CPU time spent in kernel mode.

Using the perf profiler, we see that the scaling code takes approximately 18% of the time while most of the time (54%) is taken in generating random numbers. A good example of how to use the perf profiler can be found here.

With multiple runs of the same binary, we see that the time is approximately the same with minor differences. This is because sometimes the binary might be running from the cache, consuming fewer CPU cycles, The CPU might be trying to execute multiple instructions at the same time or it might be changing the order of execution which results in different times. A better explanation can be found here.

Alternate Approaches

  1. Pre-calculate a lookup table (array) of all possible sample values multiplied by the volume factor, and look up each sample in that table to get the scaled values. The source code for this task can be found below:

The result and the time for execution were noted as below:

The time taken for the execution of this algorithm is approximately 0.300s faster than the first one. The perf profiler shows that the scaling code takes around 33% of the program runtime.

2. Convert the volume factor 0.75 to a fix-point integer by multiplying by a binary number representing a fixed-point value “1”. For example, you could use 0b100000000 (= 256 in decimal) to represent 1.00, and therefore use 0.75 * 256 = 192 for your volume factor. Multiply this fixed-point integer volume factor by each sample, then shift the result to the right the required number of bits after the multiplication (>>8 if you’re using 256 as the multiplier). The source code for this task can be found below:

The result and the time for execution were noted as below:

Using perf, it was noted that the scaling took approximately 21% of the total time and the majority of the time was taken to generate random data.

Conclusion: The codes were tested multiple times. The output produced each time remained the same. However, there were minor variations in the execution time taken.

My Fourth Pull Request for Hacktoberfest

This is my last pull request for the Hacktoberfest. For this one, I found one issue somewhat similar to the issue I worked on in Gutenberg.

I was running out of time and was anxious because I couldn’t find an issue to work one. That’s when I connnected with my partner with whom I worked to fix the issue for WordPress. She introduced this repository to me as she was working on it herself. the issue I found was a documentation issue for STL which is Microsoft’s official repository implementation of C++ Standard Library which ships as a part of MSVC toolset and the Visual Studio IDE.

MSVC’s IDE has a Task List that displays ‘TODO’ comments which can be referenced later. The repository, however, uses ‘TRANSITION’ comments for temporary things. The issue was to change ‘TODO’ comments to ‘TRANSITION’ comments so that the IDE does not pick them but will make them show up while searching for TRANSITION. In the beginning, I assumed that I had to remove the comments by itself but one of the members made the fix clearer to me.

Here’s the link to the issue and the pull request I sent. I was so excited when they merged my PR! Now I am proudly a contributor to Microsoft’s STL and I’ve completed 4 PRs for Hacktoberfest!

My Third Pull Request for Hacktoberfest

As the third week of October began, the only thing in my mind was whether I would be able to find an issue for this week. Luckily, I was able to find one. I started my search by trying to find issues from some of the famous technology companies. I was interested in fixing more bugs for Gutenberg (WordPress) but unfortunately, I couldn’t find something to work on. I finally looked into Instacart’s repository- Snacks and found one issue which I knew I could easily solve. ‘Snacks’ is Instacart’s Javascript component library.

I worked on a ‘.d.ts’ type file which provides typescript-based information about an API written in Javascript. The fix was to end the need to use the entire interface when only certain components within the interface are required. The fix was to use the Partial Interface. Partial returns the subset of the specific type it is linked to. I think I have progressed from fixing spelling errors to fixing variable names and now enhancing actual code!

Here is the link to the issue and the pull request. The best part was that there was a clear explanation of the issue and also how to fix it. Looking forward to finding my last issue now!

Design a site like this with WordPress.com
Get started