• No results found

Holistic View on Alternative Programming languages for Radio Access Network Applications in Cloud and Embedded Deployments

N/A
N/A
Protected

Academic year: 2021

Share "Holistic View on Alternative Programming languages for Radio Access Network Applications in Cloud and Embedded Deployments"

Copied!
143
0
0

Loading.... (view fulltext now)

Full text

(1)

Linköpings universitet SE–581 83 Linköping

Linköping University | Department of Computer and Information Science

Master’s thesis, 30 ECTS | Datateknik

2021 | LIU-IDA/LITH-EX-A--2021/023--SE

Holistic view on alternative

pro-gramming languages for Radio

Access Network applications in

cloud and embedded

deploy-ments

En helhetsvy över alternativa programmeringsspråk för Radio

Access Network-applikationer i moln- och inbyggda system

Anton Orö

Rasmus Karlbäck

Supervisor : John Tinnerholm Examiner : Christoph Kessler

(2)

Upphovsrätt

Detta dokument hålls tillgängligt på Internet - eller dess framtida ersättare - under 25 år från publicer-ingsdatum under förutsättning att inga extraordinära omständigheter uppstår.

Tillgång till dokumentet innebär tillstånd för var och en att läsa, ladda ner, skriva ut enstaka ko-pior för enskilt bruk och att använda det oförändrat för ickekommersiell forskning och för undervis-ning. Överföring av upphovsrätten vid en senare tidpunkt kan inte upphäva detta tillstånd. All annan användning av dokumentet kräver upphovsmannens medgivande. För att garantera äktheten, säker-heten och tillgängligsäker-heten finns lösningar av teknisk och administrativ art.

Upphovsmannens ideella rätt innefattar rätt att bli nämnd som upphovsman i den omfattning som god sed kräver vid användning av dokumentet på ovan beskrivna sätt samt skydd mot att dokumentet ändras eller presenteras i sådan form eller i sådant sammanhang som är kränkande för upphovsman-nens litterära eller konstnärliga anseende eller egenart.

För ytterligare information om Linköping University Electronic Press se förlagets hemsida http://www.ep.liu.se/.

Copyright

The publishers will keep this document online on the Internet - or its possible replacement - for a period of 25 years starting from the date of publication barring exceptional circumstances.

The online availability of the document implies permanent permission for anyone to read, to down-load, or to print out single copies for his/hers own use and to use it unchanged for non-commercial research and educational purpose. Subsequent transfers of copyright cannot revoke this permission. All other uses of the document are conditional upon the consent of the copyright owner. The publisher has taken technical and administrative measures to assure authenticity, security and accessibility.

According to intellectual property law the author has the right to be mentioned when his/her work is accessed as described above and to be protected against infringement.

For additional information about the Linköping University Electronic Press and its procedures for publication and for assurance of document integrity, please refer to its www home page: http://www.ep.liu.se/.

© Anton Orö Rasmus Karlbäck

(3)

Abstract

With the emergence of cloud based solutions, companies such as Ericsson AB have started investigating different means of modernizing current implementations of software systems. With many new programming languages emerging such as Rust and Go, inves-tigating the suitability of these languages compared to C++ can be seen as a part of this modernization process. There are many important aspects to consider when investigating the suitability of new programming languages, and this thesis makes an attempt at con-sidering most of them. Therefore both performance which is a common metric as well as development efficiency which is a less common metric, were combined to provide a holistic view. Performance was defined as CPU usage, maximum memory usage, processing time per sequence and latency at runtime, which was measured on both x86 and ARM based hardware. Development efficiency was defined as the combination of the productivity metric, the maintainability index metric and the cognitive complexity metric. Combining these two metrics resulted in two general guidelines: if the application is constantly under change and performance is not critical, Go should be the language of choice. If instead performance is critical C++ should be the language of choice. Overall, when choosing a suitable programming language, one needs to weigh development efficiency against per-formance to make a decision.

(4)

Acknowledgments

We would like to thank our excellent supervisor at LiU, John Tinnerholm, who helped pro-vide us with valuable insights and feedback throughout the thesis. We also want to thank the supervisors at Ericsson who provided invaluable help when identifying the characteristics and continually helped moving the work forward. Further, we extend our gratitude to the respondents of our survey and without your help the results would have been incomplete.

(5)

Contents

Abstract iii

Acknowledgments iv

Contents v

List of Figures viii

List of Tables x Acronyms 1 1 Introduction 2 1.1 Motivation . . . 2 1.2 Aim . . . 3 1.3 Research questions . . . 3 1.4 Delimitations . . . 4 1.5 Overview . . . 4 2 Theory 6 2.1 Radio access network . . . 6

2.2 Threads, concurrency and parallelism . . . 7

2.3 Green threads . . . 7

2.4 Inter-process communication . . . 8

2.5 The programming language C++ . . . 9

2.5.1 Concurrency . . . 9 2.5.2 Compiler . . . 9 2.5.3 Memory management . . . 10 2.6 Go . . . 10 2.6.1 Concurrency . . . 10 2.6.2 Compiler . . . 11 2.6.3 Memory management . . . 11 2.6.4 Goroutines . . . 11 2.6.5 Go Runtime system . . . 12 2.7 Rust . . . 12 2.7.1 Concurrency . . . 13 2.7.2 Compiler . . . 13 2.7.3 Memory management . . . 14 2.8 Related work . . . 14

2.8.1 Measuring performance differences for programming languages . . . . 14

2.8.2 Evaluating development efficiency differences for programming lan-guages . . . 15

(6)

2.8.4 Advantages and disadvantages of C-RAN . . . 16

2.8.5 Energy consumption evaluation of programming languages . . . 16

2.9 Soft metrics . . . 17 2.9.1 Productivity . . . 17 2.9.2 Maintainability . . . 17 2.9.3 Understandability . . . 18 3 Background 20 3.1 Ericsson . . . 20 3.2 System requirements . . . 20 3.3 System description . . . 21 3.3.1 Test driver . . . 21 3.3.2 Application . . . 22 4 Method 25 4.1 Pre study . . . 25

4.1.1 Identifying the characteristics . . . 25

4.1.2 Hardware . . . 28

4.1.3 Choosing compiler . . . 29

4.2 Implementation . . . 29

4.2.1 Detailed description of the test driver . . . 30

4.2.2 Detailed description of the IPC . . . 30

4.2.3 Detailed description of the simplified application . . . 30

4.3 Performance comparison . . . 31 4.3.1 Sequence test . . . 32 4.3.2 Latency test . . . 33 4.3.3 Encode/decode test . . . 34 4.4 Development efficiency . . . 34 5 Results 36 5.1 Performance comparison . . . 36 5.1.1 Sequence test . . . 36 5.1.2 Latency test . . . 60 5.1.3 Encode/decode test . . . 62 5.2 Development efficiency . . . 65 5.2.1 Productivity . . . 65 5.2.2 Maintainability . . . 66 5.2.3 Understandability . . . 67 6 Discussion 68 6.1 Results . . . 68 6.1.1 Performance comparison . . . 68 6.1.2 Development efficiency . . . 71 6.2 Method . . . 72 6.2.1 Replicability . . . 72 6.2.2 Reliability . . . 72 6.2.3 Validity . . . 72 6.2.4 Pre study . . . 73 6.2.5 Implementation . . . 74 6.2.6 Performance comparison . . . 75 6.2.7 Development efficiency . . . 76 6.2.8 Understandability . . . 78 6.2.9 Literature . . . 78

(7)

6.3 The authors’ experiences . . . 79

6.4 The work in a wider context . . . 80

7 Conclusion and future work 81 7.1 Conclusion . . . 81

7.2 Future work . . . 82

Bibliography 83 A Appendix 88 A.1 Time estimation survey . . . 88

A.2 Time estimation survey results . . . 94

A.3 Source code . . . 96

A.3.1 C++ application source code . . . 96

A.3.2 Go application source code . . . 107

A.3.3 Rust application source code . . . 113

A.3.4 GPB proto source code . . . 119

(8)

List of Figures

2.1 Visualization of difference between concurrent and parallel execution . . . 7

2.2 Go runtime system overview (Adapted from [goroutineImage]) . . . . 12

3.1 Test driver overview . . . 22

3.2 Application overview . . . 24

4.1 Simplified Test Driver . . . 27

4.2 Simplified Application Overview . . . 28

4.3 Sequence message flow between test driver and application . . . 33

5.1 BB2.5 CPU usage for sequence test with 10 sequences and 2 000 iterations . . . 37

5.2 BB2.0 CPU usage for sequence test with 10 sequences and 2 000 iterations . . . 37

5.3 BB3.0 CPU usage for sequence test with 10 sequences and 6 000 iterations . . . 38

5.4 Dell R630 CPU usage for sequence test with 10 sequences and 6 000 iterations . . . 38

5.5 Dell R6525 CPU usage for sequence test with 10 sequences and 6 000 iterations . . 39

5.6 BB2.5 CPU usage for sequence test with 100 sequences and 1 000 iterations . . . 39

5.7 BB2.0 CPU usage for sequence test with 100 sequences and 1 000 iterations . . . 40

5.8 BB3.0 CPU usage for sequence test with 100 sequences and 5 000 iterations . . . 40

5.9 Dell R630 CPU usage for sequence test with 100 sequences and 5 000 iterations . . 41

5.10 Dell R6525 CPU usage for sequence test with 100 sequences and 5 000 iterations . . 41

5.11 BB2.5 CPU usage for sequence test with 1 000 sequences and 400 iterations . . . 42

5.12 BB2.0 CPU usage for sequence test with 1 000 sequences and 400 iterations . . . 42

5.13 BB3.0 CPU usage for sequence test with 1 000 sequences and 1 500 iterations . . . . 43

5.14 Dell R630 CPU usage for sequence test with 1 000 sequences and 1 500 iterations . 43 5.15 Dell R6525 CPU usage for sequence test with 1 000 sequences and 1 500 iterations . 44 5.16 BB2.5 CPU usage for sequence test with 10 000 sequences and 25 iterations . . . 44

5.17 BB2.0 CPU usage for sequence test with 10 000 sequences and 25 iterations . . . 45

5.18 BB3.0 CPU usage for sequence test with 10 000 sequences and 100 iterations . . . . 45

5.19 Dell R630 CPU usage for sequence test with 10 000 sequences and 100 iterations . . 46

5.20 Dell R6525 CPU usage for sequence test with 10 000 sequences and 100 iterations . 46 5.21 BB2.5 Time per sequence for sequence test with 10 sequences and 2 000 iterations . 47 5.22 BB2.0 Time per sequence for sequence test with 10 sequences and 2 000 iterations . 47 5.23 BB3.0 Time per sequence for sequence test with 10 sequences and 6 000 iterations . 48 5.24 Dell R630 Time per sequence for sequence test with 10 sequences and 6 000 iterations 48 5.25 Dell R6525 Time per sequence for sequence test with 10 sequences and 6 000 iter-ations . . . 49

5.26 BB2.5 Time per sequence for sequence test with 100 sequences and 1 000 iterations 49 5.27 BB2.0 Time per sequence for sequence test with 100 sequences and 1 000 iterations 50 5.28 BB3.0 Time per sequence for sequence test with 100 sequences and 5 000 iterations 50 5.29 Dell R630 Time per sequence for sequence test with 100 sequences and 5 000 iter-ations . . . 51

5.30 Dell R6525 Time per sequence for sequence test with 100 sequences and 5 000 iterations . . . 51

(9)

5.31 BB2.5 Time per sequence for sequence test with 1 000 sequences and 400 iterations 52 5.32 BB2.0 Time per sequence for sequence test with 1 000 sequences and 400 iterations 52 5.33 BB3.0 Time per sequence for sequence test with 1 000 sequences and 1 500 iterations 53 5.34 Dell R630 Time per sequence for sequence test with 1 000 sequences and 1 500

iterations . . . 53

5.35 Dell R6525 Time per sequence for sequence test with 1 000 sequences and 1 500 iterations . . . 54

5.36 BB2.5 Time per sequence for sequence test with 10 000 sequences and 25 iterations 54 5.37 BB2.0 Time per sequence for sequence test with 10 000 sequences and 25 iterations 55 5.38 BB3.0 Time per sequence for sequence test with 10 000 sequences and 100 iterations 55 5.39 Dell R630 Time per sequence for sequence test with 10 000 sequences and 100 iterations . . . 56

5.40 Dell R6525 Time per sequence for sequence test with 10 000 sequences and 100 iterations . . . 56

5.41 Memory usage for sequence test for C++, Go and Rust on BB2.0 . . . 57

5.42 Memory usage for sequence test for C++, Go and Rust on BB2.5 . . . 58

5.43 Memory usage for sequence test for C++, Go and Rust on BB3.0 . . . 58

5.44 Memory usage for sequence test for C++, Go and Rust on Dell R630 . . . 59

5.45 Memory usage for sequence test for C++, Go and Rust on Dell R6525 . . . 59

5.46 BB2.5 Latency in latency test for C++, Rust and Go . . . 60

5.47 BB2.0 Latency in latency test for C++, Rust and Go . . . 60

5.48 BB3.0 Latency in latency test for C++, Rust and Go . . . 61

5.49 Dell R630 Latency in latency test for C++, Rust and Go . . . 61

5.50 Dell R6525 Latency in latency test for C++, Rust and Go . . . 62

5.51 BB2.5 Encode/decode time per sequence in encode/decode test for C++, Rust and Go . . . 63

5.52 BB2.0 Encode/decode time per sequence in encode/decode test for C++, Rust and Go . . . 63

5.53 BB3.0 Encode/decode time per sequence in encode/decode test for C++, Rust and Go . . . 64

5.54 Dell R630 Encode/decode time per sequence in encode/decode test for C++, Rust and Go . . . 64

5.55 Dell R6525 Encode/decode time per sequence in encode/decode test for C++, Rust and Go . . . 65

(10)

List of Tables

4.1 Available hardware . . . 29

4.2 Compiler configurations . . . 29

4.3 Sequence messages . . . 32

4.4 Sequence test scenarios . . . 33

5.1 Combined score development efficiency . . . 65

5.2 Weighted averages of expert estimated implementation time . . . 66

5.3 Average execution time of the application in C++, Rust and Go for 1 iteration of 10 000 sequences for all hardware configurations . . . 66

5.4 Data for the productivity metric with r=50000 . . . 66

5.5 Data for the productivity metric with r=100000 . . . 66

5.6 Data for the productivity metric with r=200000 . . . 66

5.7 Maintainability index . . . 67

(11)

Acronyms

AP Atomic Procedure.

AST Abstract Syntax Tree.

C-RAN Cloud Radio Access Network.

COTS Commercial off-the-shelf.

CP Composite Procedure.

GCC GNU Compiler Collection.

GPB Google Protocol Buffer.

ICC Intel C++ Compiler.

IEEE Institute of Electrical and Electronics Engineers.

IPC Inter-Process Communication.

RAII Resource Acquisition Is Initialization.

RAN Radio Access Network.

TCP Transmission Control Protocol.

UDP User Datagram Protocol.

UE User Equipment.

(12)

1

Introduction

This chapter gives an introduction to the problem and the context of the problem that this thesis aims to address. It also presents the research questions that this thesis will answer.

1.1

Motivation

Mobile networks are used all over the world and are the cornerstone of the networked society. To support the vast amount and diversity of data expected in future networks, Ericsson AB (Ericsson) is developing products to drive and support the networked society. This leads to the need for investigation and development of algorithms, architecture, tools etc. to support the increase of data and Massive Internet of Things1for Radio Access Network (RAN). For a more detailed explanation of RAN, see Section 2.1.

Previously, RAN applications needed dedicated hardware for optimal performance and functionality, which led to higher costs due to the fact that Commercial off-the-shelf (COTS) hardware could not be used [21]. But with the emergence of cloud environments and con-tainer technology like Docker and Kubernetes, RAN applications can now be deployed on COTS hardware and is called Cloud Radio Access Network (C-RAN) [22]. Since C-RAN is a new technology, there is a need to investigate how to implement software in order to im-prove efficiency and take advantage of the capabilities enabled by the new technology. The software needs to be able to run on different hardware and still be performant, meaning that hardware is another aspect that needs to be included in the investigation.

C++ is undoubtedly one of the most used programming languages for embedded appli-cations. RAN applications at Ericsson are also developed in C++, with the main reason being performance. There are many studies that compare different aspects such as development efficiency, CPU and memory usage for popular programming languages like Rust, Go and C++ [24, 23, 45, 28]. Furthermore, investigations in different ways of load-balancing and/or optimizing resource allocation for C-RAN [40, 52, 53, 15] exist as well. These kinds of com-parisons have a limited value for a RAN application which by nature is heavy on messaging between multiple services using asynchronous decoupled communication patterns. Another aspect that is often overlooked when designing a system, is how easy the system is to observe

1

(13)

1.2. Aim

and troubleshoot when needed. Due to the lack of comparisons specifically about perfor-mance or development efficiency of different programming languages for RAN applications, there is a need for further evaluation to determine the best suited programming language with these aspects in mind.

Another reason for comparing C++ with Rust and Go is that C++ was introduced in the ’80s, a time where 128 kB - 32 MB of RAM and 8-40 MHz single-core CPUs were the standard for computers (with larger systems having more powerful components, but nothing close to today’s standards), for example the DEC VAX 8600 was released in 1984 with 32 MB of RAM and 12.5 MHz CPU speed [8]. During the subsequent years, Moore’s law was upheld by increasing single-core clock speeds on the CPU and increased transistor counts on a sin-gle CPU core [25], meaning one could more or less expect the same program to run twice as fast when upgrading to a new computer every other year. Lately, the industry focuses on adding more cores instead, meaning that programmers have to adapt their programs to the increased core count to see improvements. Modern programming languages have also seen some improvements in regards to more efficient utilization of the CPU for concurrent pro-grams that run on a single core. Since RAN applications at Ericsson are asynchronous and single-threaded by nature, they scale horizontally to a multi-core or cloud based architecture by spawning more instances of the program. Therefore, investigating modern languages that better facilitate efficient development of performant programs that utilize the full capabilities of single-threaded programs in multi-core architectures, is highly needed for companies that plan to expand into a cloud environment.

One could also argue that the landscape of modern software development has changed into a more feature heavy focus, meaning that companies needs to patch, fix bugs and pro-vide new features in an increasingly rapid pace in order to keep up with competition. Because C++ is commonly regarded as one of the hardest or even the hardest mainstream program-ming language, companies like Ericsson have realised that they can not only focus on the performance of their products, they also have to consider things like maintainability, pro-ductivity and understandability. Not to mention the fact that young aspiring programmers might be more intrigued by developing in a more intuitive programming language with a gentle learning curve.

1.2

Aim

The aim of this thesis is to provide a holistic view on the differences between the program-ming languages C++, Rust and Go in RAN applications. To achieve this, the aim is divided into two parts: performance and development efficiency. To investigate performance in the three programming languages, a benchmark for a typical high intensity RAN use case is to be implemented in C++, Rust and Go. The purpose of the benchmark is that it should be able to give a clear picture of the performance differences of the RAN application implemented in the chosen languages. This will be accomplished by first identifying the key RAN fun-damental characteristics followed by implementing a trivial version of the real system, that still adhere to the identified characteristics and then measuring performance metrics for each programming language implementation.

To investigate development efficiency, the three metrics productivity, maintainability, and understandability will be measured. These metrics should provide a more complete eval-uation of the suitability of a programming language, compared to using only performance metrics. To accomplish this, this thesis will discuss why development efficiency metrics are important and provide relevant data for evaluating these in the different languages.

1.3

Research questions

(14)

1.4. Delimitations

1. Which of the programming languages C++, Go or Rust is best suited for developing a RAN application for a cloud and embedded deployment, with regards to performance? 2. Which of the programming languages C++, Go or Rust is best suited for developing a RAN application for a cloud and embedded deployment, with regards to development efficiency?

3. Do different types of embedded and COTS hardware affect the choice of programming language and the performance of the RAN application?

1.4

Delimitations

The first delimitation is the amount of programming languages compared, since this depends on the time it takes to create the application in the different languages. The second delimita-tion concerns the related work, which is that certain conclusions are mainly based on extrap-olation of existing studies in other contexts than to the context studied here. When it comes to the choice of programming languages to compare, the choice of C++, Rust and Go were requests made by Ericsson. For example, Erlang could have been an interesting and suitable candidate for comparison as well, but this comparison has already been done internally at Ericsson and was therefore excluded from this thesis.

Since holistic is a broad term, this thesis delimits the meaning of the word to a comparison of three different languages in regards to both performance and development efficiency as well as looking at both cloud and embedded hardware.

In the context of this thesis, performance is delimited to only include CPU-usage, memory usage, processing time per sequence and latency at runtime, and development efficiency is delimited to only include productivity, maintainability, and understandability (definitions for development efficiency metrics are given in Section 2.9).

1.5

Overview

In this section the structure of the thesis is presented and an overview of each chapter is given. All chapters were written together by the authors, except for Section 2.6 about Go which was written by Anton Orö and Section 2.7 which was written by Rasmus Karlbäck.

Background

This chapter focuses on presenting a brief introduction to Ericsson as a company, as well as an overview of the currently implemented RAN system at Ericsson that is the foundation of the research conducted in this thesis.

Theory

In this chapter, the theory related to the thesis is presented as well as a discussion about different related works.

Method

This chapter is focused on presenting the approach to solving the aim of the thesis. This was done in four main phases: a pre study phase, an implementation phase, a phase for comparing performance and a phase for comparing the development efficiency.

Results

This chapter is focused on the results obtained. The results are presented in the same order as the work that was carried out.

(15)

1.5. Overview

Discussion

In this chapter the results and the method employed to get the results are discussed. There is also a section that provides insights about the authors’ personal experiences as well as a discussion about the work in a wider context.

Conclusion and future work

This chapter provides answers to the research questions as well as suggestions for future research.

Appendix

(16)

2

Theory

In this chapter, the theory related to the thesis is presented. In Section 2.1, RAN is described. In Sections 2.2, 2.3 and 2.4 the programming concepts used in the thesis is presented. Sec-tions 2.5, 2.6 and 2.7 presents background to the programming languages C++, Go and Rust. Finally, in Section 2.8 the related works are presented and discussed.

2.1

Radio access network

This section is based entirely on the article by Checko et al. [13].

A base station consists of two main components, a baseband processing module and a radio functionalities module. Base stations are used to receive and transmit data to remote devices, such as mobile phones, which is done via antennas mounted on the base station. Equipment held by end-users that utilize base stations are called User Equipment (UE).

So far, there have been three main iterations of how the base station architecture works. The first step, the "traditional cellular" or Radio Access Network (RAN), has multiple base stations. Every single base station processes and transmits its own signal out to the core network (through something called the backhaul), since the components are integrated inside the base station. This means that all the equipment needed had to be on-site, such as backup-power, air conditioning and backhaul transmission equipment. This was mostly popular for 1G and 2G networks and meant that each base station always needs to be able to handle a maximum load, meaning a lot of its potential being unused most of the time.

When 3G was later deployed the base stations begun to have a Remote Radio Head (RRH), meaning that the base station was separated from their signal processing unit and the radio unit stayed at the base station. This signal processing unit is called a Baseband Unit (BBU) or Data Unit (DU). This meant that the BBU could be placed up to 40km from the base station and was most likely connected via an optical fiber. This in turn means that the BBU could be placed in an easier to handle location, making maintenance costs and cooling much cheaper. One could also connect multiple RRHs to a single BBU making redundancy less common.

Lastly there is the Cloud base station architecture which is not yet used. By centralizing the BBU and creating a so called "BBU pool", one can connect many RRHs to one BBU pool. A BBU pool is a cluster of multiple virtual BBUs, which can be deployed on COTS hardware. By doing this, one can scale the amount of BBU processing power needed in a very efficient way and reduce overhead costs.

(17)

2.2. Threads, concurrency and parallelism

2.2

Threads, concurrency and parallelism

It is important to differentiate between the different concepts of parallelism and concurrency, since these terms are often confused with each other and can have different meanings. The following quote by Breshears will be used as the definition of these two concepts:

A system is said to be concurrent if it can support two or more actions in progress at the same time. A system is said to be parallel if it can support two or more actions executing simultaneously. [11, p. 3]

The difference between concurrency and parallelism is visualized in Figure2.1.

Task 1 Task 2 Task 3 Task 1 Task 2 Task 3 Concurrent, non-parallel Parallel (and concurrent)

Figure 2.1: Visualization of difference between concurrent and parallel execution

In order to understand concurrency, one needs to understand what a process and a thread is first. A process is the entity that is run when launching a program, and its default be-haviour is that it does not communicate with other processes or accesses their data. Inter-Process Communication (IPC) is needed to communicate between processes, and a detailed description of the different IPC mechanisms can be seen in Section 2.4. Portable Operating System Interface Threads (pthreads) is a standard API [30] and is implemented on many dif-ferent operating systems such as Linux and macOS. A thread can be seen as a set of executable instructions that can be scheduled by the operating system [7], and a thread is part of a pro-cess. Pthreads, which are kernel-level threads, are most commonly scheduled preemptively, meaning that the scheduler can interrupt a running thread and allow another thread to run instead. This can be used when a thread is waiting for something, such as a blocking I/O operation, then another thread can be scheduled instead to utilize the CPU better [46]. A pro-cess can have one or several threads associated to it and having multiple threads that can be scheduled simultaneously is what provides thread-level parallelism in a program. Threads associated with the same process share the same resources, so a modification of a shared re-source by one thread within a process can be seen in the other threads. This might lead to race conditions, which is when two or more threads are trying to modify the same resource at the same time, leading to undefined behaviour. Since processes have their own unique mem-ory by default [46], threads within one process do not share memmem-ory with another thread in another process, unless they use IPC to share data.

2.3

Green threads

When referring to user-level threads and light-weight threads, the term green threads will be used as a collective name in this thesis. Sometimes, coroutines can be included in the term green threads, but in this thesis a decision was made to separate them. To differentiate between coroutines and green threads the explanation given by Kowalke [35] will be used. The main difference, according to the author, is that coroutines do not have a scheduler, since when a coroutine yields it passes the control directly back to its caller. Meanwhile, when a green thread yields it passes the control to the scheduler which hands control over to the next green thread in line implicitly. Green threads are similar to pthreads with two main dif-ferences, with the first being that green threads are mapped to a single pthread, effectively

(18)

2.4. Inter-process communication

making it single-threaded [51]. The second difference is that green threads are not scheduled by the operating system, they are instead scheduled by the runtime system associated with the program. This means that languages with runtimes that lack native support for green threads (for example Rust and C++), need to implement a runtime which supports manage-ment of green threads. Green threads are cooperatively scheduled and provide concurrency by multiplexing the green threads onto the pthread, thus interleaving execution at distinct and safe points. Cooperative scheduling means that green threads are not interrupted by the scheduler, instead the threads explicitly yield control to other threads at specific points and these points are usually when waiting for synchronization or for a non-blocking operation, such as non-blocking I/O or asynchronous communication.

Similar to pthreads, green threads have their own stack allocated on the heap of the pro-cess to enable context-switching, which is faster in terms of CPU-cycles compared to kernel-level thread switching [10] and uses less memory. However, the single-threaded nature of green threads means that multi-core processors are not fully utilized and green threads are therefore better suited for embedded applications, unless horizontal scaling is possible. If a green thread uses blocking operations, such as blocking I/O or synchronization, then the entire pthread is blocked, thus blocking all other green threads from running on that pthread. There exist several other names for user-level threads other than green threads, and most programming languages have their own implementation of green threads. Examples of im-plementations of green threads are goroutines1 in Go, the Boost.Fiber2 library in C++ and tokio::task3in Rust.

2.4

Inter-process communication

This section is based entirely on the guide about Inter-Process Communication (IPC) by Kalin [31].

IPC is the name of a mechanism that enables communication between different processes or more accurately, between threads of different processes. For Linux, the main IPC mecha-nisms are signals, sockets, message queues, shared files, shared memory and pipes.

Communication through signals can be achieved by interrupting an already executing program with a signal that the application should respond to. For instance, a parent process may signal to one of its child processes (created by a fork() call) that it should terminate. The child can then block or handle the signal, depending on if a signal handler for that specific signal has been implemented.

There are two variants of sockets: network sockets and Unix domain sockets. Network sockets allow communication between processes that are on different hosts using for example Transmission Control Protocol (TCP) or User Datagram Protocol (UDP) as the communication protocol. Unix domain sockets are used for communication between processes that are on the same host. Socket communication can be either unidirectional or bidirectional.

Pipes are used for First In - First Out (FIFO) communication between processes using a channel, which has a write-end and a read-end. A pipe can either be named or unnamed. The named pipe is created with a specific name by the writer and accessed through that same name when reading from that pipe. Named pipes can be used for communication between processes on different hosts with the possibility of having multiple writers and readers and exists until all processes connected to it unlinks from the pipe. Unnamed pipes can only be used for communication between a parent and its child thread, and only enables one-way communication and exists only if the parent or child uses it, or until it is closed, whichever comes first.

1https://golang.org/doc/faq#goroutines

2https://www.boost.org/doc/libs/1_68_0/libs/fiber/doc/html/fiber/overview.html 3https://docs.rs/tokio/0.2.4/tokio/task/index.html

(19)

2.5. The programming language C++

Message queues are similar to pipes. However, data does not need to be read in FIFO order, but can be read in any order, depending on the identifier of the message.

Shared storage is a basic form of IPC, meaning that one process writes to a file and another process reads from it. However, there is a risk of race conditions if the processes were to try to read and write to the same file at the same time. This is solved by using locks, where a writer blocks other writers and readers when trying to write to a file. There can be multiple readers for the same file, so readers share the same lock and when one or more readers are locking a file, it cannot be written to.

Shared memory for Linux comes in two variants: POSIX and the legacy System V API. POSIX is the newer of the two and is still under development, so depending on what ker-nel version is used there might be some portability issues. Shared memory shares the same principles for write and read access as the shared storage. This approach is also prone to race conditions, if two processes or threads are trying to access the same shared resource si-multaneously. There are two types of semaphores commonly used: binary semaphores and counting semaphores. Counting semaphores are used when there is an upper limit for shared resources. A binary semaphore is also called a mutex.

2.5

The programming language C++

C++ was created by Bjarne Stroustrup, with the aim to be the next step of the programming language C. This entailed adding object-oriented support to the C language, as well as the notion of classes and strong type checking. C++ was originally commercialized in 1985 and has had several updates since its release, with additions such as multi-inheritance, regular expression support, new syntax and new compilers [2]. C++ is a compiled language with support for data-abstraction, object-oriented programming, concurrency as well as low-level programming and is today one of the most used programming languages [61].

2.5.1

Concurrency

When C++ was originally launched it did not include features for concurrency. However, with the release of C++11, built in support for concurrency was added to the standard li-brary. This concurrency support consists of giving the programmers the ability to spawn and handle new threads and make them execute tasks. The threads launched share address space, meaning they can communicate through shared memory, see Section 2.4. Therefore, users that are using these threads needs to be aware of and utilize locks or similar mecha-nisms to avoid data races [48]. Features were also added that enabled programmers to write programs that can for example, wait for resources to be available and more [63]. With the re-cent release of C++20 constructs called coroutines were implemented, and functions related to that4. Support for green threads is provided by libraries such as the Boost.Fiber library, which can be used for concurrent single-threaded execution with a cooperative scheduler. A Boost.Fiber works like the quintessential green thread, see the definition given in Section 2.2.

2.5.2

Compiler

C++ has many different compilers, for example Visual C++5, Clang6and GCC7. For back-end compilation, different front-end compilers utilizes different back-end compilers. An example of a back-end compiler is LLVM which is used by Clang. Much like the C programming language, C++ has support for separate compilation [50]. This can be used for organizing the

4https://en.cppreference.com/w/cpp/language/coroutines 5https://docs.microsoft.com/en-us/cpp/?view=msvc-160 6https://clang.llvm.org/

(20)

2.6. Go

code into smaller fragments, with a header file describing the interface or class contents, and an implementation file containing the implementation.

2.5.3

Memory management

C++ has what is called manual memory allocation, meaning that the programmer is respon-sible for allocating heap memory. Memory allocated also needs to be deallocated manually in C++ [49]. This is done by using destructors and "delete" operators for example. The rec-ommended way to make memory management easier in C++ is to use a design pattern called Resource Acquisition Is Initialization (RAII)8[9]. RAII describes the life-cycle of a resource, saying that it has to be acquired before use and the resource is destructed after use. How-ever, this can also be handled automatically if using different C++ constructs such as smart pointers9or more low-level solutions such as manually de-allocating memory.

2.6

Go

Go was introduced in November 2009 and has been under development since. It was created by Robert Griesemer, Rob Pike and Ken Thompson who worked for Google and the reason for creating Go was their frustration with existing programming languages [55]. The main goal for Go was to create a language that could be efficient when compiling and executing as well as effective, expressive and reliable when writing the code [19]. Since C++ was developed in the ’80s it did not have access to the same amount of memory, cores and other modern components that are present today. Therefore, C++ was focused on a single block of memory and a single processor, meaning utilizing modern hardware components required a bit of effort. The motivation behind Go was a language that could efficiently and easily utilize modern components natively and therefore fill the same space that C++ did in the 1980s [14]. The resulting language is a statically typed language that has automatic garbage collec-tion and can be described as a language heavily influenced by C++, but with emphasis on safety and simplicity [19]. From C++, Go inherited its expression syntax, control-flow state-ments, basic data types, call-by-value parameter passing, pointers and also the compilation into machine code. It does however have an unusual approach to object-oriented program-ming since objects cannot be created per se, instead structs that contain the data linked to the "object" have to be created. Go is a general-purpose language and can be used for everything from small purpose-built software to larger integrated systems. Companies like Google and Netflix use Go in many different projects, such as computing recommendations10for Netflix

users and dl.google.com11for serving Google downloads.

The downside of Go is that at the time of writing it does not support generics, meaning for example some repetition of code and increased complexity when testing [54]. Another downside is the handling of objects in Go. Objects are, as mentioned before, treated differ-ently which can lead to some confusion or problems for new developers.

2.6.1

Concurrency

The Go language has built-in support for concurrency and the constructs are called goroutines (see Section 2.6.4) [20]. The usage of goroutines makes the Go process run onto a smaller set of operating-system threads and is very similar to how Erlang handles concurrency. Simply put, a goroutine is a function call that completes in an asynchronous manner. The Go compiler can either use a single thread and use different timer signals to switch between these threads, or

8https://en.cppreference.com/w/cpp/language/raii 9https://en.cppreference.com/book/intro/smart_pointers 10https://github.com/Netflix/rend

(21)

2.6. Go

the compiler can spawn an OS thread for each goroutine. The threads communicate through channels which enables non-blocking communication of their current state.

2.6.2

Compiler

The default compiler for Go is called Gc and is written in Go based on the Plan 9 loader [56]. Gc used to be written in C, but was converted to Go so that Go could be self-hosting. However, users can decide if they want to use another front end compiler called Gccgo which is written in C++ instead, this couples to the standard GCC backend.

2.6.3

Memory management

The language Go can be more CPU intensive than languages like C++ and Rust since it has automatic garbage collection [27]. The collector is used to remove unused heap elements meaning it can free up memory leading to less memory (RAM) consumption, but at the cost of CPU performance.

2.6.4

Goroutines

This section is based on the research by Deshpande et al. [18]. For the purpose of this the-sis there is no need to go in-depth about the channels, only the scheduler and goroutines themselves are interesting.

Goroutines consist of three different structures and the most important task of these is to keep track of their stack and their current state. These three structs are called the G struct (goroutine), M struct (OS-thread/machine) and P struct (Processor). The G struct represents the goroutine itself, and its most important responsibility is to keep track of the stack of and the state of the routine as well as references to the code it is supposed to run. The M struct is the runtime system representation of an OS-level thread. It mainly keeps track of all G’s (both running and waiting in a queue), its own cache and current locks being held. Finally, there is the P struct, which contains two different G queues, one for G’s waiting to run (i.e. ready to be picked up by M’s) and one for free G’s. There is also a queue of M’s that are idle, as well as a global scheduler queue which contains G’s.

All M’s that are spawned are assigned to some P, a M cannot execute code without being assigned to a P. If all the M’s are making blocking calls or are being run, the M queue in the P is empty. G’s can spawn more M’s if needed up to a maximum of a user set maximum variable named GOMAXPROCS. At any time at most one G can be run per M and only one M can run per P. The reason for having P’s is for instance if a M makes a system call, then that M is "detached" from their P while doing the system call, and the P can handle their next M. But as soon as the detached M is finished with their system call and wants to run the remainder of the code, they need to be assigned to a P first. Figure 2.2 shows three P’s running G’s on M’s in parallel. Green means they are running, red means they are waiting. This is a simplistic view, since there might be several thousands of G’s and multiple detached M’s.

(22)

2.7. Rust P P P M M M M M M M G G G G G G G G G G G G G G G G G G G G G G G G G G G Global Queue

P local queue P local queue P local queue

Figure 2.2: Go runtime system overview (Adapted from [47])

Now the scheduler needs to assign G’s to the different M’s in a fast way. The scheduler does this by simply finding a G that is runnable in the P queue or the global queue and then executes this. The way this works is with work-stealing. If a G is either created or changed state into runnable, it becomes available in the local queue of ready G’s in the P. When a P finishes executing one of their G’s, they try to take one G from their local queue but if this queue is empty it will instead steal half of another P’s local G queue. What P it steals from is chosen at random.

2.6.5

Go Runtime system

Since Go has high-level support for automatic garbage collection, goroutines and channels it needs to have an effective runtime system infrastructure [18]. In Go, every call that the user-written code is making to the operating system goes through the runtime system layer so it can effectively handle things like scheduling and garbage collection. However, the most important work performed by the runtime system is arguably to keep track of and handle the scheduling of the goroutines.

2.7

Rust

Rust was created by Graydon Hoare and started as a side-project in 2006 [59] that later led to the first stable release coming out 2015 and has since seen regular releases every six weeks [58]. The three main goals of Rust is performance, reliability and productivity. The language has support for object oriented programming, is statically typed, guarantees thread and mem-ory safety, provides memmem-ory management enforced by RAII instead of using a garbage col-lector [43] and provides integration with other programming languages [60].

(23)

2.7. Rust

2.7.1

Concurrency

One of the major goals of Rust is to provide safe and efficient concurrency mechanisms [33, Chapter 16]. This is achieved by ownership and type-checking, meaning that most errors related to concurrency will be detected at compile-time instead of runtime. The consequence of this is that subtle bugs that otherwise would be missed by some compilers and found in specific runtime system scenarios can be found and corrected at an early stage.

Rust achieves concurrency by letting the programmer implement multi-threading. For the threads to be able to communicate with each other, the Rust standard library provides two ways to do so: message passing using channels and through shared memory. Channels are split into a receiver and a transmitter, where there can be multiple transmitters (i.e. multiple threads producing data) but only one receiver (i.e. one thread consuming the data). The receiver can either be blocking or non-blocking. When it is blocking the thread will just wait for incoming messages on the channel without executing any other code during the wait, and if it is non-blocking it will periodically check if there are any messages on the channel and perform other work in the meantime. Rust handles safe concurrency when it comes to channels by enforcing ownership rules, which means that when a message has been sent from a transmitter to a receiver, the message can no longer be accessed from the transmitter. This check is done during compile-time, so an incorrect reference to a message that is no longer owned by the current thread will produce an error before it is even run, meaning that the incorrect code cannot be run [33, Chapter 16].

The Rust version of green threads are called tokio::task (task) which use async/await syn-tax combined with the Tokio runtime12. Tasks correspond to the definiton of green threads given in Section 2.2.

2.7.2

Compiler

This section is based on the Rust Compiler developer guide [57].

The Rust compiler (rustc) is self-compiling and it is written in Rust and uses LLVM as its backend. The compilation process is started when the user invokes the rustc command on Rust source code, with optional command line arguments. Thereafter, the source code is converted into token streams by a low-level lexer, which then are passed to a high-level lexer that validates the tokens and performs interning on them. Interning is a technique where, during compilation, many values are allocated in something called an arena. Values in the arena are immutable and can be compared at a low cost, by comparing pointers and is use-ful for preventing duplicate values, for example types or identical strings. Interning is used for optimizing memory allocation and performance by only allocating memory once, instead of several times. After the lexers are done, the token streams are forwarded to the parser that creates an Abstract Syntax Tree (AST) and does additional validation. The AST is then converted to a High-Level Intermediate Representation (HIR), which in turn is converted to a Mid-Level Intermediate Representation (MIR) and Typed High-Level Intermediate Represen-tation (THIR). HIR is used for type inference, THIR for pattern and exhaustiveness checking and MIR for borrow checking. Exhaustiveness checking is used to validate match-expressions and a match-expression is valid if all possible branches are covered, otherwise it is not exhaus-tive. Match-expressions that are not exhaustive will then throw a compile-time error. The MIR is optimized and monomorphized (copying generic code and replacing type parameters with concrete types) before moving on to code generation, which entails converting the MIR to a LLVM Intermediate Representation and passing it to the LLVM backend to generate ma-chine code. The last step of the compilation process is linking the binaries and libraries to create the final binary file that can be executed.

(24)

2.8. Related work

2.7.3

Memory management

To guarantee memory safety, Rust uses ownership which is a key feature of the language. The three rules of ownership in Rust are: every value in Rust has an owner, a value can only have one current owner and when the owner is no longer relevant, the value is dropped [33, Chapter 4]. What this means is that memory allocation and deallocation is done automatically when an owner goes out of scope. The owner is in charge of deallocating all of its values, by calling the Drop method implemented by its values, which clears the allocated memory on the heap. Checks for violations of ownership rules are done at compile-time, meaning that potential memory leaks and violations will result in a compile-time error [33, Chapter 4].

2.8

Related work

This section focuses on previous work in related areas, such as performance comparisons, C-RAN investigations and different metrics for measuring soft qualities of software.

2.8.1

Measuring performance differences for programming languages

Measuring the performance of different programming languages has been relevant for al-most as long as there have been multiple programming languages. One of the al-most basic, but also most important and relevant ways to compare programming languages is by bench-marking using the same type of program and measure aspects such as CPU-usage, processing efficiency and memory usage. When comparing the performance of different programming languages, there need to exist one or several concrete problems or use cases that should be implemented in the different languages. The same type of programming mechanisms should be employed when creating the solution to the problem, e.g. the solution should use con-currency. Serfass and Tang [45] did a comparison of Go and C++ Threading Building Blocks (TBB), which is a library that enables tasks to be run on multiple cores in parallel. How-ever, this comparison was done on a very specific use case and problem, so the result that C++ TBB has better performance than Go is not generally applicable to other problems. For instance, the performance of the C++ TBB library is not relevant to the performance of the C++ boost::fibers library, meaning it cannot directly be applied in this thesis. However, the same general approach for answering the research questions will be used, namely identify the use case, identify what type of problem it poses and see if it translates into a similar but more simplified problem, propose a solution for the simplified problem and then implement a solution.

In the paper by Memeti et al. [38], the authors describe a way of comparing four dif-ferent programming frameworks (OpenMP, OpenCL, OpenACC, CUDA), in regards to pro-gramming productivity, performance, and energy consumption. The way of measuring the programming productivity, by comparing how many lines of code that are needed to pro-vide multi-threaded support for an application, is very usable for this thesis. This method, or some variation of it, can be applied when evaluating development efficiency of different programming languages. The way of keeping the same hardware and using an external tool with little overhead for measuring performance of the programs will also be useful. How-ever, a different tool than the one used in the paper by Memeti et al. is needed for this thesis, since it is a library for C++ and cannot be used for other programming languages.

The paper by Sachs et al. [44] focuses on constructing a benchmark for Message-oriented Middleware (MOM), with a focus on using real-life data instead of synthesized data. Also, their aim is to provide a benchmarking program suitable for the entire MOM server and its functionalities, instead of isolating specific parts which had been done in previous works according to the authors. The benchmark the authors produced is called SPECjms2007, which can use custom workloads in order to test either the MOM application as a whole, or selected components. Given the fact that a RAN application is similar in nature to a MOM application,

(25)

2.8. Related work

it would be appropriate to use real-life data from the actual system when implementing the RAN application. However, this would make the scope of the thesis too large and instead synthesized data will be used.

2.8.2

Evaluating development efficiency differences for programming languages

The article by Ardito et al. [5] did a comparison of the verbosity, understandability, organi-zation, complexity and maintainability of Rust to C, C++, Python, JavaScript and TypeScript. The metrics used in the comparison were lines of code, number of methods, number of ar-guments, number of exit points, Cyclomatic Complexity, Cognitive Complexity, Halstead metrics and Maintainability Index. The source code used in the comparison were nine al-gorithms commonly used when benchmarking performance of programming languages and was taken from the Energy-Languages’ github repository13. The results of this comparison was that code written in Rust is more readable, more organized, less verbose and equally maintainable compared to C and C++. Compared to Python, JavaScript and Typescript, Rust had lower maintainability. The authors also point out that Rust had the lowest Cognitive Complexity of all the investigated languages, indicating that the understandability of the Rust source code was better than the other languages. From this article, it is reasonable to assume that the development efficiency compared in this thesis will provide a better score for Rust compared to C++. However, it does not give an indication of how Go will score compared to either Rust or C++. Additionally, some of the metrics used for determining the softer aspects of programming languages will be included in this thesis.

Constanza et al. [17] had a more informal approach when comparing the ease of program-ming in C++, Go and Java. The authors had to decide which language would be more suit-able for their sequencing tool elPrep, where memory management and manipulation of large amounts of data is important. Therefore languages with support for safe reference counting and concurrent, parallel garbage collection was chosen to be compared. The authors ap-proach to decide which language to use was to implement a nontrivial subset of elPrep and then benchmark these different implementations. From these performance benchmarks they settled for Go. But since the tool will get regular updates and need maintenance they also realised that softer aspects, such as how difficult it is to implement new features, needs to be investigated as well. To solve which language was the easiest to implement new features in they simply compared the different challenges encountered and the solutions to said chal-lenges. Their conclusions from this was that C++ required the most development effort since there are more options that needs to be compared (allocate on stack/heap, deciding on mem-ory managers etc.). Go and Java was found to be comparable, where some features where easier in Go, and some in Java. From this article we can see that it is important to consider softer aspects when deciding on a programming language, however a more formal approach would be useful since the conclusions by Constanza et al. are quite subjective.

2.8.3

Comparing compilers

In the paper by Sagonas et al. [34] the authors compare three different backend compilers for the Erlang language. The compilers compared were ErLLVM, High Performance Erlang (HiPE) and BEAM (Björn’s Erlang Abstract Machine). The motivation behind their compari-son was that the authors had come up with the new backend compiler ErLLVM, which build on LLVM, and wanted to see how it compared to existing compilers. The paper describes in detail how they changed the LLVM to make ErLLVM and how ErLLVM works. From the performance comparison the conclusion was drawn that the new ErLLVM was significantly faster than BEAM and achieves similar performance as existing HiPE implementations. One could also see a difference in compile time for the different compilers.

(26)

2.8. Related work

The paper by Machado et al. [36] compared the performance between the compilers GNU Compiler Collection (GCC) and Intel C++ Compiler (ICC), with different optimisation flags and on two different multi-core architectures. The algorithm chosen for performance eval-uation was the Embarrassingly Parallel (EP) kernel [6]. The results of the study were that the most optimized sequential execution time for the Intel architecture was 778 seconds and for the AMD architecture it was 997 seconds for the GCC compiler. For the ICC compiler, the optimized sequential execution time decreased to 525 seconds for Intel and 776 seconds for AMD. This represents roughly a 33% decrease for the Intel architecture and roughly 23% decrease for the AMD architecture. For multi-core execution, the EP kernel was implemented in Pthreads, C++11, Cilk Plus, OpenMP and TBB. The result of the multi-core execution was that the execution time increase varied greatly between the different multithreaded imple-mentations.

The papers by Sagonas et al. [34] and Machado et al. [36] show that different compil-ers and compiler configurations can have an impact on runtime system performance, which needs to be investigated and compared when doing similar studies and will therefore be con-sidered for this thesis. Although suitable, an investigation of different compilers will not be done in this thesis, since the time investment of doing such an investigation would be too large. For example, if we chose to investigate just three different compilers (excluding dif-ferent configurations of these) in each language on each hardware, this would result in 45 unique combinations to be tested and compared.

2.8.4

Advantages and disadvantages of C-RAN

One advantage of C-RAN is that it is more cost efficient than its predecessors, since multiple BBUs can be grouped in a co-located BBU Pool, thus lowering energy consumption [13]. Also, the co-location of BBUs combined with virtualization of BBUs gives the ability to adjust to a change in user-demand, which further decreases energy consumption and improves scala-bility [42]. The work presented by Tang et al. [52] also presents the advantage of reducing cost in a C-RAN solution compared to traditional solutions. They found that according to simulations the performance and cost are more beneficial than previous methods.

The disadvantages of the C-RAN solution are that one needs to have a BBU pool nearby, otherwise the transportation cost will be high. There is also a need for a high amount of bandwidth in order to transport the necessary information to the BBU pool, since it can be connected to up to 1000 base stations [13]. One also needs to make sure that the BBU pool is reliable and that the cells in the pool are optimally assigned, to make the savings worth it.

2.8.5

Energy consumption evaluation of programming languages

The paper by Pereira et al. [41] investigated if there was a correlation between the aspects execution time, energy consumption and peak memory usage. The authors wanted to in-vestigate if programs with shorter execution time also consumed less energy. Ten different benchmarking programs implemented in twenty seven programming languages were com-pared in terms of execution time, energy consumption and peak memory usage. The con-clusion from the paper was that a faster language is not always more energy efficient. If only looking at the languages that are also compared in this thesis, and considering all three aspects, then the conclusion according to their data is that Go is the best language. But for embedded applications, execution time and energy usage might be more relevant than peak memory usage. Therefore, taking only execution time and energy usage into account, Rust is the best choice, followed by C++.

(27)

2.9. Soft metrics

2.9

Soft metrics

When considering the scale of the code base of companies like Ericsson, qualities like per-formance are no longer the only important metric when measuring whether a programming language is good for them to use or not. Companies should also consider the "softer" aspects such as readability, usability etc, since for example the readability of a program or language correlates to its maintainability. Therefore, for the purpose of this thesis, the focus will be on productivity, maintainability and understandability.

2.9.1

Productivity

For the context of this thesis the definition of productivity provided by Institute of Electrical and Electronics Engineers as the “ratio of work product to work effort” [29] will be used. A method for measuring performance productivity is presented by Schreiber et al. [32] and the authors suggested a mathematical expression for productivity of a programming language compared to a "base language" as seen in Section 2.4. P denotes the problem of interest, subscript 0 means the base program and subscript L is the L program (the program written in the programming language to compare). The ease of use, or relative power of the language ρLis given by Equation 2.1,

ρL = I(P0)

I(PL) (2.1)

where I(P0)is the implementation time of the base program and I(PL)is the implementation

time of program L.

The performance of the language, or the efficiency eLis given by Equation 2.2,

eL= E(P0)

E(PL)

(2.2)

where E(P0) is the average execution time per run for the base program and E(PL) is the

average execution time per run for program L.

The problem-dependent parameter X is given by Equation 2.3,

X=rE(PL)

I(PL) (2.3)

where r is a weighting factor that is specific to the current problem that reflects how impor-tant it is to minimize execution time compared to implementation time, E(PL)is the average

execution time per run and I(PL)is the implementation time.

The productivity of a language compared to the base language is seen in Equation 2.4. productivity= ρL+eLX

1+X (2.4)

The authors suggest that these metrics provide a good estimation of how the productivity can be measured on a specific problem implemented in different languages.

2.9.2

Maintainability

For the context of this thesis the first (1) definition of maintainability provided by IEEE as the “ease with which a software system or component can be modified to change or add capabilities, correct faults or defects, improve performance or other attributes, or adapt to a changed environment” [29, p. 258] will be used.

(28)

2.9. Soft metrics

Coleman et al. presented in 1994 a polynomial assessment model for measuring main-tainability of code [16]. The formula they presented as mainmain-tainability index was:

Maintainability=171 ´ 5.2 ˚ ln aveVol ´ 0.23 ˚ aveV(g1)´

16.2 ˚ ln aveLOC+ (50 ˚ sina2.46 ˚ perCM) (2.5)

Where aveVol is the average Halstead volume metric, aveV(g’) is the average cyclomatic com-plexity, aveLOC is the average lines of code and finally perCM is the percentage of comments in the code. The Halstead volume metric [26] V can be calculated as

V=N ˚ log2(η) (2.6)

where N= N1+N2and η =η1+η2. N1is the total number of operators, N2the total number

of operands, η1the number of distinct operators and η2the number of distinct operands. The

Halstead volume can be interpreted as a metric for how much information a reader of the code has to ingest in order to understand the code.

Cyclomatic complexity [37] can be calculated using the control flow graph of the code (where each node is a basic block). The metric was first introduced in 1976 by Thomas Mc-cabe. The method can for example, accurately predict the number of test cases that will be needed to test code. In essence, cyclomatic complexity metric M can be determined as:

M=E ´ N+2P (2.7)

where E are the number of edges of the graph, N is the number of nodes of the graph and P is number of connected components. A very simple example of this would be a program that only has one if-statement. This code would only have two paths, either the if-condition is True or False, meaning the cyclomatic complexity would be two.

In this thesis, the version of maintainability index presented by Visual Studio will be used instead14. Their version normalizes the value to lie between 0 and 100 instead of the original ´8to 171. Visual Studio also removed the comments part, which is not of any interest in this thesis either. The formula used is:

Maintainability index=MAX(0,(171 ´ 5.2 ˚ ln Vol´

0.23 ˚ V(g1)´16.2 ˚ ln LOC)˚100/171) (2.8)

2.9.3

Understandability

For the context of this thesis the definition of understandability provided by IEEE as the “ease with which a system can be comprehended at both the system-organizational and detailed-statement levels” [29, p. 485] will be used. This thesis will focus on the detailed-detailed-statement level.

As previously stated, metrics like understandability are increasingly important for com-panies involved in software development. However, these metrics are hard to measure and therefore new methods are developed to this day. One example of such a metric is Cognitive Complexity which was presented in 2017 by a company called SonarSource [12]. One could argue that this is in ways a modernization and/or extension of cyclomatic complexity, since cyclomatic complexity for example does not measure more modern structures such as try/-catch. This method was developed as a way of measuring the understandability of code [12], unlike cyclomatic complexity which mainly focuses on maintainability and testability. One important part of the cognitive complexity metric is that it does not rely on mathematical models to assess the code, instead it uses assessment rules that represent the mental effort of understanding code.

14

(29)

2.9. Soft metrics

Since this was just recently published by a company, one could question the validity and correctness of the method. This was investigated in a study by Muñoz Barón, Wyrich, and Wagner [39] and they concluded that cognitive complexity is a promising metric for mea-suring the quality goal understandability. They found a correlation of the time it took for a developer to understand the code with a combination of time and correctness.

Cognitive complexity is based on three simple rules [12]:

• Ignore the structures that allow multiple statements to be readably shorthanded into one.

• Increment (add one) for each break in the linear flow of the code. Examples are GOTO, recursive methods and break/continue.

• Increment when flow-breaking structures are nested. Example of this would be a two-dimensional nested loop, which would increase the score to two (i.e. one per level), and an If-statement inside the nested loop would increment once more to a three.

There are four different types of increments in cognitive complexity: nesting, structural, fun-damental and hybrid. Nesting increments occur when there are nested control-flow struc-tures, for example an if-statement within a lambda-function. Nesting increments do not di-rectly contribute to the cognitive complexity score, but instead make other increments within a nested structure more expensive. Using lambda functions does not increment the total score but increases nesting with +1, so an if statement within a lambda would give +2 (due to the nesting increment) instead of +1 if it would not be nested. Structural increments are control-flow structures within a nested statement that also increase the nesting increment, for example an if-statement within an if-statement. Fundamental increments are used for state-ments that are not nested, such as binary expressions or methods within a recursive loop. Thus, the use of extensive recursion will yield a high score for cognitive complexity. Hybrid increments are used for control flow structures that are not nested but increase the nesting value. Examples of hybrid increments are else-if statements and else statements [12].

(30)

3

Background

This chapter focuses on presenting a brief introduction to Ericsson as a company, as well as an overview of the currently implemented RAN system at Ericsson that is the foundation of the research conducted in this thesis. The system is divided into two parts, a test driver and an application. By studying the given system, the goal was to identify the key characteristics of the RAN application in order to create a simplified version of the system with the same characteristics that requires less effort to test and implement in other languages. In this chap-ter, the term system under test (SUT) will be used when referring to the system implemented by the authors of this thesis.

3.1

Ericsson

Ericsson is a Swedish networking and telecommunications equipment company with 100 824 employees (as of December 2020). Ericsson was founded in 1876 by Lars Magnus Ericsson as a telegraph repair company. Today, Ericsson is leading the implementation of 5G technologies and holds more than 57 000 patents. Ericsson has offices all around the world, in about 180 countries and their products are used world-wide [1].

3.2

System requirements

The research in this thesis is done on behalf of Ericsson. Therefore, Ericsson had a list of requirements for the SUT which were:

1. The characteristics identified in the original RAN system at Ericsson shall be the same as in the SUT.

2. The communication between the test driver and SUT shall use some kind of IPC, and the test driver should preferably spread the start of new sequences in time to mimic telecommunication applications.

3. The SUT shall use green threads (see Section 2.3) with context switching. 4. The SUT shall be single-kernel-threaded and use green threads for concurrency.

References

Related documents

46 Konkreta exempel skulle kunna vara främjandeinsatser för affärsänglar/affärsängelnätverk, skapa arenor där aktörer från utbuds- och efterfrågesidan kan mötas eller

The increasing availability of data and attention to services has increased the understanding of the contribution of services to innovation and productivity in

Parallellmarknader innebär dock inte en drivkraft för en grön omställning Ökad andel direktförsäljning räddar många lokala producenter och kan tyckas utgöra en drivkraft

Närmare 90 procent av de statliga medlen (intäkter och utgifter) för näringslivets klimatomställning går till generella styrmedel, det vill säga styrmedel som påverkar

I dag uppgår denna del av befolkningen till knappt 4 200 personer och år 2030 beräknas det finnas drygt 4 800 personer i Gällivare kommun som är 65 år eller äldre i

Den förbättrade tillgängligheten berör framför allt boende i områden med en mycket hög eller hög tillgänglighet till tätorter, men även antalet personer med längre än

Detta projekt utvecklar policymixen för strategin Smart industri (Näringsdepartementet, 2016a). En av anledningarna till en stark avgränsning är att analysen bygger på djupa

DIN representerar Tyskland i ISO och CEN, och har en permanent plats i ISO:s råd. Det ger dem en bra position för att påverka strategiska frågor inom den internationella