Memory managment¶

Python and Javascript are interpreted languages and have a garbage collector. So the runtime takes care of the memory. This has the advantage that the programmer does not need to think about memory. The disadvantage is that he cannot control it and thus you can get unpredictable stalls in your program performance, when the garbage collector (typicall with mark and sweep algorithm) kicks in.

This is why compiled languages c++ and c are used in operating systems such as linux (c) and windows (c++).

In [1]:
import ROOT
In [2]:
import fortranmagic
%load_ext fortranmagic
import os
import sys

import numpy as np

if sys.platform.startswith("win"):
        # Depends of system, python builds, and compilers compatibility.
        # See below.
    f_config = "--fcompiler=gnu95 --compiler=mingw32"
else:
        # For Unix, compilers are usually more compatible.
    f_config = ""

    # Disable only deprecated NumPy API warning without disable any APIs.
f_config += " --extra '-DNPY_NO_DEPRECATED_API=0'"

%fortran_config {f_config}
New default arguments for %fortran:
	 --extra '-DNPY_NO_DEPRECATED_API=0'

c++¶

In [3]:
%%cpp
class C{
public:
    C(){
        cout<<"created\n";
    }
    virtual ~C(){
        cout<<"destructed\n";
    }
    int a;
    int b;
};

//main(){
    C c;          //lives on stack
    {
        C c;
    }
    
//}
created
created
destructed

if things need to live longer than the scope you need to use the heap. We have used the heap already with make_shared. Here we used a shared smart pointer to take care of creating and deleting the objects in memory through counting how many variables are pointing to it.

Internally objects are created with new and delete which generates pointers. it is recommendable to use the smart pointers (more in the next section):

In [4]:
%%cpp -d
class Class{
public:
    Class(){
        cout<<"created\n";
    }
    virtual ~Class(){
        cout<<"destructed\n";
    }
    int a;
    int b;
};

Class * createClass(){
    Class *c=new Class;
    return c;
}
In [5]:
%%cpp
//void main(){
    Class *d;
    d=createClass();
    delete d;
//}
created
destructed

You can also create dynamic c type arrays.

In [6]:
%%cpp

int i=100;
int *a=new int[i];

delete[] a;

There are three important smart pointers:

  • unique_ptr
  • shared_ptr
  • weak_ptr

We start with the unique_ptr that has unique ownership of the object:

In [7]:
%%cpp
#include<memory>
using namespace std;
{
    unique_ptr<C> p=make_unique<C>();
    unique_ptr<C> p2=std::move(p);
    p2->a=10;
    cout <<p2->a<<endl;
    //cout <<p->a<<endl;//if you comment this out the program crashes.
}
created
10
destructed

Then there is the shared_ptr for shared owenersip. It uses reference count to decide if when the object should be freed.

In [8]:
%%cpp
#include<memory>
using namespace std;
{
    shared_ptr<C> p=make_shared<C>();
    shared_ptr<C> p2=p;
    p2->a=10;
    cout <<p2->a<<endl;
    cout <<p->a<<endl;
}
created
10
10
destructed

you can convert from unique_ptr to shared_ptr but not the other direction. So unique pointer is the best return type from a library function if you have dynamic data.

In [9]:
%%cpp
#include<memory>
using namespace std;
{
    std::unique_ptr<std::string> unique = std::make_unique<std::string>("test");
    std::shared_ptr<std::string> shared = std::move(unique); //only this is allowed
    //std::unique_ptr<std::string> unique2 = std::move(shared); //not allowed
}

One problem of shared_ptr that implements reference counting are circular references where objects A points to object B and vice versa. Then they are never deleted. In those cases we need a week pointer, that does not change reference count but can be expired as seen below:

In [10]:
%%cpp -d
#include <iostream>
#include <memory>
 
std::weak_ptr<int> gw;
 
void observe()
{
    std::cout << "gw.use_count() == " << gw.use_count() << "; ";
    // we have to make a copy of shared pointer before usage:
    if (std::shared_ptr<int> spt = gw.lock())
        std::cout << "*spt == " << *spt << '\n';
    else
        std::cout << "gw is expired\n";
}
In [11]:
%%cpp

 
//int main()
//{
    {
        auto sp = std::make_shared<int>(42);
        gw = sp;
 
        observe();
    }
 
    observe();
//}
gw.use_count() == 1; *spt == 42
gw.use_count() == 0; gw is expired

c¶

c does not have smart pointer. you need to use malloc and free to manage the memory.

In [12]:
%%cpp 

int *a=(int*)malloc(sizeof(int)*100);
a[0]=1;
a[1]=2;
free(a);

Javascript¶

Memory is managed by mark and sweep garbage collector. It basically goes through all variables on marks the object. Then all that are not marked are deleted. You can mark objects to be deleted by calling: variablename=null

In [13]:
%%js //the next line is only necessary in jupyter notebooks
element.setAttribute('style', 'white-space: pre;');console.log=function(text){element.textContent+=text+"\n"}

class Geometry{
    constructor() {
        if (this.constructor == Geometry) {
            throw new Error("Abstract classes can't be instantiated.");
        }
    }
    getArea(){
        throw new Error("Method 'getArea()' must be implemented.");
    }
    getCircumference(){
        throw new Error("Method 'getCircumference()' must be implemented.");
    }
}

class Rectangle extends Geometry{
    constructor(width, height){
        super();
        this.width=width
        this.height=height
    }
    getArea(){
        return this.width*this.height;
    }
    getCircumference(){
        return 2*this.width+this.height;
    }
}

let c=new Rectangle(1,2);

console.log(c)
c=null
console.log(c)

Python¶

Memory is managed by mark and sweep garbage collector. But you can mark objects to be deleted by calling: del variablename

In [14]:
a=[10,20,30]
print(a)
del a
print(a)
[10, 20, 30]
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[14], line 4
      2 print(a)
      3 del a
----> 4 print(a)

NameError: name 'a' is not defined

Fortran¶

array on stack

In [ ]:
%%fortran 

! program and subroutine exchanged due to jupyternotebook

! program main
subroutine main()
    implicit none
    real*8,dimension(1:3) :: a=(/ 10,20,30 /)

    ! Variables

    ! Body of fortranterst
    print *,a
    
    ! read * ! this does not work in jupyter
! end program
end subroutine main
In [ ]:
main()

Fortran¶

array on heap

In [ ]:
%%fortran 

! program and subroutine exchanged due to jupyternotebook

! program main
subroutine main()
    implicit none
        real*8,dimension(:),allocatable :: a
        real*8,dimension(:,:),allocatable :: M
        real*8::dotprodresult
        allocate(a(1:3))
        allocate(M(1:3,1:3))
        
        a=(/1,2,3/)
        M=transpose(reshape((/ 1, 2, 3, 4, 5, 6, 7, 8, 9 /), shape(M)))
        
        print *,matmul(M,a)
        print *,dot_product(a,a)   
        
        ! read * ! this does not work in jupyter    
        deallocate(a)
        deallocate(M)
! end program
end subroutine main
In [ ]:
main()