Tuesday, August 27, 2024

My journey of learning Rust - Static dispatch vs Dynamic dispatch...

 In Rust, the Static dispatch means the compiler determines which method to call at compile time. This is in contrast to Dynamic dispatch in which the decision is made during runtime. Static dispatch is the default behaviour in Rust. Static dispatch is used with generics and trait bounds without using the keyword dyn.

Static dispatch is usually faster than dynamic dispatch because there is no need for a runtime lookup in the VTble of the method to be called.

Example of Static Dispatch and Dynamic Dispatch

Please have a look at the below code which has used both Static dispatch as well as Dynamic Dispatch.

trait Animal {
fn make_sound(&self);
fn wag_tail(&self){

println!("i don't have a tail..."); //default behavior
}
}

struct Human;

impl Animal for Human {
fn make_sound(&self) {
println!("Human is speaking...");
}
}

struct Dog;

impl Animal for Dog {

fn make_sound(&self) {
println!("Dog barks...");
}

fn wag_tail(&self) {
println!("The dog is waging it's tail");
}
}

fn static_dispatch_makesound<T : Animal> (animal:T){
animal.make_sound();
}

fn dynamic_dispatch_makesound(animal : &dyn Animal) {
animal.make_sound()
}

fn static_dispatch_wagtail<T : Animal> (animal:T){
animal.wag_tail();
}

fn dynamic_dispatch_wagtail(animal : &dyn Animal) {
animal.wag_tail();
}

fn main() {

let dog = Dog;
let human = Human;

dynamic_dispatch_wagtail(&dog);
static_dispatch_makesound(dog);

dynamic_dispatch_makesound(&human);
static_dispatch_wagtail(human);
}

Please note how static_dispatch has used trait bound function 

fn static_dispatch_makesound<T : Animal> (animal:T){
animal.make_sound();
}

and dynamic dispatch has used the keyword dyn.

fn dynamic_dispatch_wagtail(animal : &dyn Animal){ animal.wag_tail();

Static dispatch is fast and allows function calls to be inlined, which is key for optimization. 

However, Static dispatch can lead to "code bloat" because the binary contains multiple copies of the same function, one for each type.

Sunday, August 25, 2024

From Object Oriented Analysis and Design to Language like Rust - a major paradigm shift in the programming world...


चरैवेति, चरैवेति - Charaiveti, Charaiveti - keep walking...

because the motion is the life - we need to move on to keep the balance of life...

We must not stop.

The movement is essential.

Life is like a bicycle.

The moment it stops - the balance goes for a toss.

So here we go... moving forward from C++/Java/OOAD to Rust...

In Rust... i keep my Trust...

The first language that I learned in the software world was C++ - that was back in mid '90s. From that time onwards, almost 30 years have passed by. During this time the software world was dominated by C++, Java and other OOAD stuffs.

I experienced the use cases of Unified Modelling Language and it's usefulness in the design process of an object oriented software project.

From C to C++ and then Java, it was the first major paradigm shift.

And now, it seems we are going to experience another paradigm shift in the world of software.

With language like Rust picking up the popularity, will demand from the programmers to shift the gear - from maybe C++ or Java to Rust.

Rust is neither a pure object oriented like Java nor an absolute functional language. Rather it has taken the good stuffs from both Object oriented domain and functional domain to offer an unique experience to the programmers.

It's definitely a major paradigm shift in the world of computer programming.

Let us try to understand some features and you will feel the differences.

The first major difference is that Rust has avoided the class based inheritance model of standard OOAD domain. In the OOAD domain we encapsulate the state and the behaviour together in a class and we extend that class, which is known as inheritance to offer versatility in the behaviours.

In Rust, we define Traits and use Traits (which are similar to Interface in the OOAD world) and composition rather than inheritance. This approach encourages composition over inheritance, meaning that instead of creating deep hierarchies, you build functionality by combining smaller, focused pieces.

The next obvious difference is the way we handle memory related issues in C++ and Java. In Java, we have garbage collection and in modern C++, we handle it through Boost's library and pointers.

However, Rust uses its unique ownership model. Each value in Rust has a single owner, and memory is automatically reclaimed when the owner goes out of scope. Additionally, Rust uses borrowing and lifetimes to ensure that references to data do not outlive the data itself, preventing many common errors like null pointer dereferences or data races.

The other paradigm shift is the way Rust handles polymorphism. Polymorphism in standard OOAD is done using inheritance. The shift in Rust involves moving from a default of dynamic dispatch and inheritance to a system that favours static dispatch (monomorphization) with the option for dynamic dispatch when necessary. This results in more performant code but requires more deliberate design decisions about when to use dynamic behaviour. Static dispatch may increase the code foot print but it will be much faster than dynamic dispatch and concepts like VTble.

There are also differences between a standard OOAD exception handling and the way Rust handles it using Result and Option.

I have heard that the way Rust manages concurrency is also kind of a paradigm shift in the thought processing of an OOAD engineer. However, I am yet to cover all those things and hence no comment. for the time being.

Now let us try to understand some of the nitty gritties of Rust vis-a-vis  UML as this was the back bone in the world of OOAD design.

You will be surprised to know that we can't get similar effects of Aggregation in Rust because of it's lifetime management. We can simulate the Agrregation in Rust. Nevertheless it won't be exact aggregation.

Look at the following piece of code.

struct Engine {
    horsepower: u32,
}
struct Car<'a> {
    engine: &'a Engine,  // Aggregation-like relationship
}
fn main() {
    let engine = Engine { horsepower: 300 };
    let car = Car { engine: &engine };
    println!("Car has an engine with {} horsepower.",             car.engine.horsepower);
}


Explanation:

&'a Engine: The Car struct contains a reference to an Engine. This setup means that the Engine can exist independently of the Car, similar to how aggregation works in UML.

Ownership and Borrowing: Unlike aggregation in traditional OOPS, Rust's borrowing rules ensure that you cannot accidentally leave a reference dangling (i.e., referencing an object that no longer exists). That means once the car is destroyed, it will take away the engine with it - unlike the traditional OOAD aggregation.

So what is the alternative? Use Ownership - much like Composition in UML. Look at the following piece of code.

struct Engine {
    horsepower: u32,
}

struct Car {
    engine: Engine,  // Composition-like relationship
}

fn main() {
    let engine = Engine { horsepower: 300 };
    let car = Car { engine };  // The Car now owns the engine

    println!("Car has an engine with {} horsepower.", car.engine.horsepower);


This is a Work In Progress.

Will gradually add other aspects of Rust in the near future.

For all the engineers who embraced the Gang of Four design pattern book as their skill set, will have to embrace the paradigm shift in the world of programming.

It's time to embrace changes - and keep this book as a great influencer for the OOAD programmers.



Thursday, August 15, 2024

C++ Concurrent profiling using Helgrind - a tool of Valgrind

 On 15th August - my contribution to the learning community...

Concurrency profiling in C++ is essential for optimizing the performance of multi-threaded applications by identifying and addressing bottlenecks, inefficiencies, and issues like race conditions and deadlocks.

I am using the Helgrind tool of Valgrind in eclipse to do the experimentation on C++ data race condition in a multithreaded application.

A data race occurs in a multithreaded application when two or more threads access shared data concurrently, and at least one of these accesses is a write operation without proper synchronization (e.g., without locks). This can lead to unpredictable behavior, crashes, or incorrect program output.

Here is some information about Helgrind.

Helgrind:

- Detects data races, potential deadlocks, and lock-order violations.

- Useful for debugging multi-threaded applications where data consistency is crucial.

Data Race Detection: 

If Helgrind detects that two threads are accessing the same memory location concurrently without proper synchronization, and at least one of these accesses is a write, it flags this as a data race.

Please have a look at my video - it's all explained here.


Concurrency profiling in C++ is crucial for developing high-performance multi-threaded applications.

Valgrind is a versatile tool for detecting a wide range of memory-related issues in C++ applications. Tools like Memcheck, Helgrind, DRD, and Massif provide comprehensive coverage of memory leaks, invalid accesses, uninitialized memory usage, threading issues, and memory management inefficiencies.

Using Valgrind in the development cycle can significantly improve the stability and performance of your application by identifying and allowing you to fix these memory-related issues.

Another important task that Helgrind does is to check whether there is any deadlock in a multithreaded C++ application - like Cyclic dependency.

Cyclic dependency deadlock occurs when two or more locks are acquired in a different order in two task executions, potentially leading to a deadlock when the program's tasks execute in parallel.

A Lock order violation problem indicates the following timeline:

Task 1

Acquire lock A.

Acquire lock B.

Release lock B.

Release lock A.

Task 2

Acquire lock B.

Acquire lock A.

Release lock A.

Release lock B.

If these time lines are interleaved when the two tasks execute in parallel, a Deadlock occurs:

Task 1: Acquire lock A.

Task 2: Acquire lock B.

Task 1: Try to acquire lock B; wait until task 2 releases it.

Task 2: Try to acquire lock A; wait until task 1 releases it.

For example consider the following piece of code

//============================================================================

// Name : CyclicDependencyDeadLock.cpp

// Author : Som

// Version :

// Copyright : som-itsolutions

// Description : Hello World in C++, Ansi-style

//============================================================================


#include <iostream>

#include <thread>

#include <mutex>


using namespace std;


std::mutex lock1, lock2;


void threadA() {

int count = 0;

std::lock_guard<std::mutex> guard1(lock1);

for (int i = 0; i<100000; i++){

count++;

}

std::lock_guard<std::mutex> guard2(lock2);

//cout<<"Thread id " <<this_thread::get_id()<<" this is okay now because of correct lock order " <<count<<endl;

cout<<"Thread id " <<this_thread::get_id()<< " this will never be printed..."<<count<<endl;

}


void threadB() {

std::lock_guard<std::mutex> guard2(lock2);

int count = 0;

for (int i = 0; i<100000; i++){

count++;

}

std::lock_guard<std::mutex> guard1(lock1);

//cout<<"Thread id " <<this_thread::get_id()<<" this is okay now because of correct lock order " <<count<<endl;

cout<<"Thread id " <<this_thread::get_id()<< " this will never be printed..."<<count<<endl;

}


int main() {

std::thread t1(threadA);

std::thread t2(threadB);


t1.join();

t2.join();


return 0;

}



And if we profile the above piece of code using Helgrind, it will show

Thread #3: lock order "0x10E160 before 0x10E1A0" violated...

Please have a look at the following video...


To avoid the lock order violation we must go for consistent global order for all the threads.

That's all for today...

I hope this exploration will help the inquisitive minds of software engineers.

Jai Hind.... Jai Bharat...

Wednesday, August 14, 2024

Memory profiling of C++ using Valgrind...


On the eve of 15th August - my contribution to the learning community...

Profiling in C++ is a crucial step in optimizing the performance of your application. It involves analyzing various aspects of the code, such as CPU usage, memory consumption, and execution time, to identify bottlenecks and inefficiencies. 

Types of Profiling

CPU Profiling:

Focuses on the amount of time the CPU spends executing different parts of the program.

Identifies functions or code segments that take up most of the CPU time.

Tools: gprof, perf, Visual Studio Profiler.

Memory Profiling:

Monitors the application's memory usage, including allocations, deallocations, and memory leaks.

Helps in identifying memory-intensive operations and optimizing memory management.

Tools: valgrind, Massif, AddressSanitizer, Heaptrack.

I/O Profiling:

Analyzes input/output operations, such as disk access or network communication.

Useful for applications that are I/O bound.

Tools: strace, iotop.

Concurrency Profiling:

Monitors the behavior of multi-threaded programs to identify issues like thread contention, deadlocks, and inefficient parallelism.

Tools: Intel VTune, ThreadSanitizer, gprof.

Today I will discuss about memory profiling - especially memory leak in a C++ program using valgrind.

Please have a look at my video.

It's well-explained here.


Valgrind is a versatile tool for detecting a wide range of memory-related issues in C++ applications. Tools like Memcheck, Helgrind, DRD, and Massif provide comprehensive coverage of memory leaks, invalid accesses, uninitialized memory usage, threading issues, and memory management inefficiencies.

Using Valgrind in the development cycle can significantly improve the stability and performance of your application by identifying and allowing you to fix these memory-related issues.

Jai Hind.... Jai Bharat...

Thursday, August 8, 2024

CyclicBarrier in Java Concurrency package...

 A CyclicBarrier is a synchronization tool in Java that allows a group of threads to wait for each other to reach a common point before proceeding. Once all threads reach this point (the barrier), they are released simultaneously. The term "cyclic" implies that the barrier can be reused multiple times.

Key Features of CyclicBarrier

  1. Barrier Point: The point where all participating threads must wait before any of them can proceed. Once the last thread reaches this barrier, the barrier is broken, and all threads continue execution.

  2. Cyclic Nature: The barrier is "cyclic" because it can be reused after the waiting threads are released. This allows the same CyclicBarrier instance to be used for multiple cycles of synchronization.

  3. Optional Runnable Action: You can specify a Runnable action that will be executed once the last thread reaches the barrier. This action is executed before the threads are released.

Source Code:


Class StudentTask

package com.somitsolutions.java.cyclicbarrier; import java.time.LocalTime; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class StudentTask implements Runnable { private String name = ""; private int timeLapseBeforeStarting = 0; private CyclicBarrier cyclicBarrier = null; public StudentTask(String name, int timeLapseBeforeStarting, CyclicBarrier cyclicBarrier) { this.name = name; this.timeLapseBeforeStarting = timeLapseBeforeStarting; this.cyclicBarrier = cyclicBarrier; } @Override public void run() { // TODO Auto-generated method stub try { Thread.sleep(timeLapseBeforeStarting); System.out.println("Thread " + this.name + " has reached the barrier and will wait"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { this.cyclicBarrier.await(); LocalTime localTime = LocalTime.now(); System.out.println(this.name + " is starting at " + localTime .getHour() + " : " + localTime.getMinute() +" : " + localTime.getSecond()); } catch (InterruptedException | BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }


Class classEight:


package com.somitsolutions.java.cyclicbarrier;


import java.util.concurrent.CyclicBarrier;


public class ClassEight {

static final int NUMBER_OF_STUDENTS = 3;

static final CyclicBarrier cb = new CyclicBarrier(NUMBER_OF_STUDENTS, ()->{

System.out.println("All threads have reached the barrier. All of them will now proceed...");

});

Thread ridit = new Thread(new StudentTask("Ridit", 1000, cb));

Thread ishan = new Thread(new StudentTask("Ishan", 2000, cb));

Thread manav = new Thread(new StudentTask("Manav", 3000, cb));

public void giveTaskToStudent() {

ridit.start();

ishan.start();

manav.start();

}


}



class Main :


package com.somitsolutions.java.cyclicbarrier;


public class Main {


public static void main(String[] args) {

// TODO Auto-generated method stub

ClassEight etc = new ClassEight();

etc.giveTaskToStudent();


}


}

The Output:

Thread Ridit has reached the barrier and will wait

Thread Ishan has reached the barrier and will wait

Thread Manav has reached the barrier and will wait

All threads have reached the barrier. All of them will now proceed

Ishan is starting at 20 : 21 : 31

Manav is starting at 20 : 21 : 31

Ridit is starting at 20 : 21 : 31


Here's my discussion on Barrier in C++ 20.


Wednesday, August 7, 2024

Why is Turbo C++ still being used in Indian schools and colleges?

Have a look at the screenshot...



Wrote this post at Quora almost 6 years ago. 

Here's the link...

https://qr.ae/p2NPOl

Today I asked ChatGPT about the latest status... 

and to my surprise, I got the above answer... 

I am a software professional with many years of experience. Now I train and coach others in different software technologies. Many of my students are from CBSE 10+2 and they use Turbo C++ at school.

When I asked them why their school is still using it, they said probably the CBSE board has asked the schools to do so. 

This is really a dangerous trend - training students with obsolete technologies. Turbo C++ is really prehistoric and it does not have the standard header files of modern C++ (like std :: string). I am not sure why CBSE has not banned Turbo C++ in school. 

While at my training institute, I use Eclipse with CDT plugin alongside Cygwin & GCC compiler and I install the same on my students’ computers, but this should be a normal way of dealing with software rather than an exception.

No wonder the Indian graduates mostly have half-baked knowledge and remain unemployable for a long time till they learn by rote learning about a few historical events and general knowledge and crack an equally boring Govt. job…


Babus in the Department of Education must be progressive.


It's time for a collective introspection - there is no shortcut to build skills - many night outs in front of a computer is the only answer. To clean up the swamp we must shake up the education system as practised in today's Bharat...


Is anybody listening?


Wake up, please - or else the news like the Rs. 20000 salary per month for a fresh engineering graduate which is even less than that of a driver or a house maid, will dominate the Indian corporate sectors.