There are about 700 programming languages in the world. However, developers use about 20 different programming languages to build enterprise software. In other words, there are only a few popular general-purpose programming languages, even if there are many programming languages. Developers usually start coding at school, university, or when they begin their career.
When they start coding, every developer find themselves asking one question: Which programming language should I learn first? If you studied computer science at a university, the syllabus usually has the C programming language first.
The C programming language is still used frequently in hardware-related software projects.
Human-friendly syntax and semantics
Full-featured standard APIs
Rich frameworks and libraries ecosystem
On the other hand, the modern community doesn’t often use C — other popular languages offer a more friendly, easy, and flexible environment than C. Yes, C is a better choice for hardware-related projects, but the majority of developers work with web and mobile-related projects.
Let me explain why learning C is the best choice.
C makes you a good problem solver
When you find those scenarios, you’ll face problems that need to be solved. Practicing algorithmic questions is a great way to become a good problem solver. We don’t always work with straightforward tasks that involve only the standard library and built-in features of your favorite enterprise programming language. We often work with tasks involved with problem-solving skills. So, writing your initial codes with C makes you a better problem solver.
Moreover, developers who participate in competitive programming hackathons often use C to solve problems.
C gives you the smell of hardware
Programming languages like Python, C#, and Java are very human-friendly languages. However, those languages are very abstracted from physical hardware. In other words, you won’t get the experience of the behavior of computer hardware until you start start programming with C. Modern programming languages hide the entire hardware-related experience, offering a brand new sandboxed environment. In most cases, this sandboxed environment is created with a virtual machine.
Unfortunately, developers skip the crucial hardware-related topics like memory management, file handling, and code-optimization — because they don’t start with C. Modern programming languages automatically handle memory allocation and deallocation with garbage collectors. On the other hand, in the C programming language, it’s up to the developer to manage the memory by writing highly optimized code.
Writing your initial codes with C gives you an unforgettable journey around hardware that every computer scientist should experience.
C teaches you about performance and freedom
When a programming language offers very human-friendly abstraction, the particular programming language will become less flexible. Every standard library method and built-in method of your favorite programming library acts as a hard-coded black-box. In other words, modern programming languages hide the low-level code and offer clean but limited interfaces for developers. Direct dynamic memory allocation is literally impossible with modern programming languages. Meanwhile, C gives you true freedom by exposing all low-level code access.
C compilers produce blazing-fast assembly code. Therefore, the C development environment itself motivates you to write high-performance code. In C, we have to carefully declare variables, allocate memory, clean memory, access resources, and release resources. If you initially worked with C, you may not use excessive memory, unwanted resources, and wrong data structures with the programming language that you currently work in.
C motivates you to write clean code
You have to write many code lines with C, unlike modern programming languages. This is because C offers low-level access to everything you need — it doesn’t give you a highly abstracted standard library. When the number of lines in the code increases, the complexity of the code also increases. So, we have to write a clean and self-explanatory code to get rid of messy code.
Writing clean code is a skill in high demand when we work with industry-level software projects. In fact, writing clean code is a piece of cake if we’ve worked on a C-based project.
With the active development of the C++ project, C has become a subset of C++. C++ is indeed a modern programming language, with a full-featured standard library. Therefore, learning C++ is not the same as learning C. However, the direct memory manipulation capabilities and low-level access are still there. Almost all modern programming languages compete with each other — by introducing new syntax, semantics, and standard library methods. But, languages like Go only extend the standard libraries and community-driven libraries.
Chose the hard route first with the C programming language. It will help you to become an expert in your favorite programming language.
Microprocessor with vector instructions is going to be the big thing for the future. Why? Because self-driving, speech recognition, image recognition are all based on machine learning and machine learning is all about matrices and vectors.
But that is not the only reason. We have been banging our heads in the wall trying to eke out more performance for years ever since we semi-officially declared Moore’s laws to be over. In the golden old days of microprocessor design, we could simply double the clock frequency of the CPU each year and boom everybody was happy. That wonderful old trick is over.
Performance increases has been stalling, creating a need to utilize more transistors for parallel processing in different ways, whether multi-core, vector-processing or out-of-order execution.
Today we play a thousand different clever games to eek out more performance whether that is through adding more CPU cores, adding out-of-order execution, more advance branch predictors or SIMD instructions.
All of these tricks really boil down to one central idea: Trying to find ways of doing work in parallel. Whenever you loop over an array of elements and do some computation on each of these elements, you have an opportunity for data-parallelism. This loop could with clever compilers be turned into a bunch of SIMD or vector instructions.
With Single-Instruction-Multiple-Data (SIMD) instructions unlike normal Single-Instruction-Single-Data (SISD) instructions each instruction (green) processes multiple independent streams of data (blue).
SIMD instructions such as Neon, MMX, SSE2 and AVX have worked great in multimedia applications. Doing things like video-encoding e.g. But we need to to squeeze out more performance in more areas. Vector instructions offer a lot more flexibility in taking almost any loop and turning it into vector instructions. However there are lots of different ways of going about this.
I covered RISC-V vector instructions here: RISC-V Vector Instructions vs ARM and x86 SIMD.
Lately I covered ARM vector instructions: ARMv9: What is the Big Deal?.
While writing this last article I struggled. Nothing seemed to work the way I had been taught. I thought I sort of knew vector instructions from having researched them for my first article. Thus after finishing the last story, I began comparing notes.
This made me realize that ARM and RISC-V actually follow a profoundly different strategy. This is worth covering, in particular because it touches upon some of my cherished topics. I love simple and elegant and efficient technology: The Value of Simplicity.
The RISC-V vector extensions when contrasted with ARM SVE is a study in elegant simplicity.
The Problem with ARM Scalable Vector Instructions (SVE)
While researching SVE, it was not obvious to me why I struggled to grasp it, but when picking up my RISC-V book and re-reading the vector-extensions chapter it became clear.
LD1D z0.d, p0/z, [x10] # Load double word (64-bit) elements
In this case the predicate register p0 determined exactly how many elements we are loading. If p0 = 1110000 e.g. then we are loading three elements. v0 is the 128-bit lower part of z0.
Registers with the Same Name?
The reason for that is that the d and v and z registers are in the same spot. Let me clarify. You got a block of memory called a register file inside every CPU. Or to be more specific, you can have multiple register files in a CPU. The register file is the memory holding the registers. So you don't access memory cells in the register file like regular main memory. Instead you refer to sections of it using register names.
The ARM code is slightly shorter, as may ARM instruction do a lot more than one thing. This is one thing I think makes RISC-V code a lot easier to read. Instructions tend to just do one thing. There is not a lot of special syntax to deal with. Just notice something simple like loading the vector registers how complex that is with ARM:
LD1D z1.d, p0/z, [x1, x3, LSL #3]
The square bracket part computes the address to load data from:
[x1, x3, LSL #3] = x1 + x3*2³ = x[i * 8]
So you can see x1 represent the base address of the x variable. x3 is the i counter. By doing a 3-left shift we get eight, which is the number of bytes in a 64-bit floating point number.
As a beginner to vector coding, I must say that ARM is just way too complicated. Not because ARM is bad. I have also looked at Intel AVX instructions and that looks 10x worse. I am most definitely not going to spend time understanding AVX, given the efforts required to grasp SVE and Neon.
To me it is pretty clear that for anyone who want to learn assembly coding, you should really start with RISC-V. For beginners it is just magnitudes easier to follow. And that isn’t surprising. It was specifically designed to be taught at universities.
Architectures like Intel x86 is complex for legacy reasons. It has been around for decades and attempted to maintain backwards compatibility. ARM in contrast is a cleaner design but made complicated simply because industry is what primarily dictates design and not teachability or beginner friendliness.
If you are a hobbyist like me, who just wants to keep up to date with how technology is evolving and what things like vector processing is, then safe yourself a lot of trouble and just read a RISC-V book.
People may argue that ARM or Intel or whatever is easier because there are more books, and more resources. No fricken way! I can tell you from my own experience over the last couple of days, that all that documentation is frequently more of an obstacle than help. It means you need to dig through way more material. You got a lot of contradictory stuff based on old ways of doing things.
If you want to get into assembly coding, you can read some of my articles and tutorials:
- in the United States, consumer spending on pizza delivery reached roughly USD 14 billion in 2020, growing by around USD 3 billion between 2019 and 2020.