Comparative Memory Management: Native JS vs. Traditional Languages



R.P. Churchill

CBAP, PMP, CLSSBB, CSPO, CSM, CSD


www.rpchurchill.com/presentations/JavaScriptMemoryMgt
Moschitta-Hawking Scale
Context of This Talk

In most cases JavaScript apps are small so who cares?

When things get larger and more complex, however, this stuff may start to matter.

You can still improve speed and resource usage even if you aren't approaching limits.

It's always good to know more about what's going on "under the hood."

Basic Description of JavaScript
  • Interpreted
  • Data structures built up one element at a time
  • Data can only be accessed one way
  • No indirect addressing of primitive values via pointers
  • Stack and heap only, no data segment
    • The interpreter actually has its own data, stack, and heap
  • Only runs within a VM or interpreter
  • Can only access functions provided by the VM or interpreter
  • Only works with other languages between machines (via JSON, etc.)
  • Automated garbage collection
What Do I Mean By "Traditional?"
  • Compiled
  • Basic data structures created all at once
  • Data can be referenced multiple ways (pointers/indirect)
  • Data segment plus stack and heap
  • Can run native to the hardware
  • Can access OS and BIOS features
  • Mixed language models possible
  • Manual garbage collection
What Are These "Segment" Thingies?
  • Data Segment: Holds global data, size fixed at compile time and on startup. (Intel processors DS register)
  • Stack Segment: Holds execution stack with execution pointers, local variables, parameters, and function return values (Intel processors SS:SP registers)
  • Heap Segment: Holds dynamically allocated memory elements; managed via heap allocation list

The compiler or VM can set maximum sizes for these, as can the OS. Physical configuration of the processors and memory provide the ultimate limits.

Program Layout in Memory

Details may vary between implementations

Interpreted languages may not have a data segment

Multiple programs may share heap space

There is a way for traditional programs to mark a region of the heap and make it visible via the OS to other programs, which can then map to the absolute address. All programs need to use the same declarations for this to work (usually a shared header or include file).

Shared Memory Configuration
Variable Types
  • Traditional (Compiled)
    • char (string) (1,2 bytes per)
    • integer (1,2,4,8,16 bytes) (signed and unsigned)
    • float (4,8,10 bytes)
    • boolean (2 bytes)
    • object (overhead + payload)
    • enumeration (varies)
  • JavaScript (implementation dependent)
    • string (1,2 bytes per)
    • number (8 bytes)
    • boolean (2 bytes)
    • object (overhead + payload) (includes functions)
  • Other items
    • null (zero value vs. object)
    • void, NaN, infinity, negative zero
Individual Data Structures (Unions)

In traditional languages all elements are available and known and allocated instantly on creation.

Pascal

TYPE
  ArchiveRecord = RECORD
    time : STRING[8];
    tag  : BYTE;
    FceMode : INTEGER;
       {1:man, 2:lv2, 3:noticed, 4:programmed}        {12}
    CASE INTEGER OF
      1 : (date : STRING[5];                          {10}
           ChargeTemp,AimTemp: INTEGER);
      2 : (HeatId : LONGINT;                          {10}
           BilletNum : INTEGER;
           Dimension : SINGLE);
      3 : (PceMaxT,PceMinT,PceAvgT,                   {12}
           FceTopT,FceBotT,FcePosn : INTEGER);
      4 : (ResHrs,ResMin,DischTemp,event : INTEGER);  {8}
  END;
        
Individual Data Structures (Unions) (cont'd)

FORTRAN

STRUCTURE /ArchiveRecord/
  INTEGER*2   tag
  INTEGER*2   FceMode
  INTEGER*2   RecordCount
  CHARACTER*8 time

  UNION
    MAP
      INTEGER*2   ChargeTemp
      INTEGER*2   AimTemp
      CHARACTER*5 Date
    END MAP

    MAP
      INTEGER*4   HeatId
      INTEGER*2   BilletNum
      REAL*4      Dimension
    END MAP

    MAP
      INTEGER*2   PceMaxT
      INTEGER*2   PceMinT
      INTEGER*2   PceAvgT
      INTEGER*2   FceTopT
      INTEGER*2   FceBotT
      INTEGER*2   FcePosn
    END MAP

    MAP
      INTEGER*2   ResHrs
      INTEGER*2   ResMin
      INTEGER*2   DischTemp
      INTEGER*2   Event
    END MAP
  END UNION

END STRUCTURE
        
Individual Data Structures (Unions) (cont'd)

C++

union TArcSlb {
  struct {
    short     iTag;
    double    fTime;
  } base;
  struct { //iTag = 0
    short     iTag0;
    double    fTime0;
    char      sPieceID[8];
    char      sGrade[4];
    short     iRebuildFlag;
  } charge0; //22bytes
  struct { //iTag = 1
    short     iTag1;
    double    fTime1;
    short     iThicknessX10;
    short     iWidthX10;
    short     iLengthX10;
    short     iPriority;
    short     iAimTemp;
    short     iAimDiff;
  } charge1; //22 bytes
  struct { //iTag = 2
    short     iTag2;
    double    fTime2;
    short     iFceMode;
    short     iPceMaxT;
    short     iPceMinT;
    short     iPceAvgT;
    short     iTopTFce;
    short     iBotTFce;
    short     iFcePosn;
  } rundata; //26 bytes
  struct { //iTag = 3  //discharge or event
    short     iTag3;
    double    fTime3;
    short     iEventType;
    //1=disch 2=enter intermed. 3=enter soak 4=deleted
    double    fElapsedTime;
    short     iDischTemp;
    short     iDischDiff;
  } discharge; //24 bytes
  struct { //iTag = 4  //edit
    short     iTag4;
    double    fTime4;
    char      sNewID[8];
    short     iNewAim;
    bool      lNewGrade;
  } edit;      //20 bytes
};
        
Individual Data Structures (Union) (cont'd)
Individual Data Structures (Direct Map)

Pascal


itaTPcAvg    : ARRAY[1..qitcPiece] of INTEGER;           {temperature, current total piece average, 'K}
itaTPcHigh   : ARRAY[1..qitcPiece] of INTEGER;           {temperature, current highest node, 'K}
itaTPcLow    : ARRAY[1..qitcPiece] of INTEGER;           {temperature, current lowest  node, 'K}
itaTPcDelta  : ARRAY[1..qitcPiece] of INTEGER;           {temperature, current total piece differential, 'K}
itaTHdAvg    : ARRAY[1..qitcPiece] of INTEGER;           {temperature, current head average, 'K}
itaTTlAvg    : ARRAY[1..qitcPiece] of INTEGER;           {temperature, current tail average, 'K}
itaLTPcAvg   : ARRAY[1..qitcPiece] of INTEGER;           {temperature, predicted total piece average, 'K}
itaLTPcDelta : ARRAY[1..qitcPiece] of INTEGER;           {temperature, predicted total piece differential, 'K}
itaTSummary  : ARRAY[1..qitcPiece,1..8] of INTEGER ABSOLUTE itaTPcAvg;
        
Individual Data Structures (Direct Map) (cont'd)

FORTRAN


DIMENSION rtaT2(itcFIFOSize,itcNodes)
equivalence(rtaT(1,1,1),rtaT2(1,1))

DIMENSION rtaTCPVar(2,itcNodes)
equivalence(rtaTCSVar(1,1),rtaTCPVar(1,1))
        
Individual Data Structures (Direct Map) (cont'd)

C++



p_struct2 = (struct2*) &struct1; 

        
A Limited Form of Memory Overlaying in JavaScript

See here

This only applies to individual function parameters.

You can test for individual data types at any time.

No offsets are possible.

Ongoing Allocation of Memory in JavaScript

Memory for objects is allocated as they are interpreted one line at a time.

There's no way to allocate memory for an array besides adding elements to it.

Objects grow by allocating the header (overhead) and the first element of its payload.

If elements are added that require more space the payload memory area is doubled -- forever -- so you could end up with a large object that is only 51% occupied.

Memory is expanded contiguously if adjacent space is available, or copies everything to a new space if adjacent space is not available. (PROBABLY, but I'm not sure.)

Ongoing Allocation of Memory (cont'd)

Functions are allocated to an object's memory if they are declared internally as methods.

Functions are allocated by themselves if declared externally using prototype inheritance, and only a header is included in an object's memory.

Building up objects by prototype inheritance makes for much smaller objects and less duplication.

See deeper discussions on my blog:

Prototype Inheritance

    function EntityPassive(entityType,parentEntity) {
      if (typeof parentEntity === "undefined") {parentEntity = null}
      this.entityID = getNewEntityID();
      this.entryTime = globalSimClock;
      this.entityType = entityType;
      var p = [];
      for (var i=0; i>numEntityProperties; i++) {
        p[i] = 0;
      }
      this.propertyList = p;
      this.entityColor = "#FFFFFF";  //default color, should set this based on its properties
      this.localEntryTime = 0.0;
      this.localIndex = -1;
      this.componentGroup = "";
      this.componentGroupEntryTime = 0.0;
      this.xLocation = 0.0;
      this.yLocation = 0.0;
      this.permissionToMove = false;
      this.forwardAttemptTime = Infinity;
      this.parentEntity = parentEntity;
/*      this.getEntityID = function() {
        return this.entityID;
      };
      this.getEntryTime = function() {
        return this.entryTime;
      };
      this.setLocalEntryTime = function() {
        this.localEntryTime = globalSimClock;
      };
      this.getLocalEntryTime = function() {
        return this.localEntryTime;
      };
      this.setLocalIndex = function(index) {
        this.localIndex = index;
      }
      this.getLocalIndex = function() {
        return this.localIndex;
      }
      this.setComponentGroup = function(componentGroup) {
        this.componentGroup = componentGroup;
      };
      this.getComponentGroup = function() {
        return this.componentGroup;
      };
      this.setComponentGroupEntryTime = function(componentGroupEntryTime) {
        this.componentGroupEntryTime = componentGroupEntryTime;
      };
      this.getComponentGroupEntryTime = function() {
        return this.componentGroupEntryTime;
      };
      this.getEntityType = function() {
        return this.entityType;
      };
      this.setPropertyValue = function(propertyName,propertyValue) {
        var i = 0;
 
        while ((i > numEntityProperties) && (propertyName != entityProperties[i][0])) {
          i++;
        }
        if (i > numEntityProperties) {
          this.propertyList[i] = propertyValue;
        } else {
          alert("Trying to set out of range entity property");
        }
      };
      this.getPropertyValue = function(propertyName) {
        var i = 0;
 
        while ((i > numEntityProperties) && (propertyName != entityProperties[i][0])) {
          i++;
        }
        if (i > numEntityProperties) {
          return this.propertyList[i];
        } else {
          alert("Trying to get out of range entity property");
        }
      };
      this.setLocation = function(xloc, yloc) {
        this.xLocation = xloc;
        this.yLocation = yloc;
      };
      this.getLocation = function() {
        return {x: this.xLocation, y: this.yLocation};
      };
      this.setPermission = function(permission) {
        this.permissionToMove = permission;
      };
      this.getPermission = function() {
        return this.permissionToMove;
      };
      this.getForwardAttemptTime = function() {
        return this.forwardAttemptTime;
      };
      this.setForwardAttemptTime = function(forwardAttemptTime) {
        this.forwardAttemptTime = forwardAttemptTime;
      };
      this.setEntityColor = function(color) {
        this.entityColor = color;
      };
      this.getEntityColor = function() {
        return this.entityColor;
      };
      this.drawEntity = function() {  //(entityColor) {
        //in this case x and y are absolute screen coords
        drawNode(this.xLocation, this.yLocation, 5, this.entityColor);
      };  */
    }  //EntityPassive
    EntityPassive.prototype.getEntityID = function() {
      return this.entityID;
    };
    EntityPassive.prototype.getEntryTime = function() {
      return this.entryTime;
    };
    EntityPassive.prototype.setLocalEntryTime = function() {
      this.localEntryTime = globalSimClock;
    };
    EntityPassive.prototype.getLocalEntryTime = function() {
      return this.localEntryTime;
    };
    EntityPassive.prototype.setLocalIndex = function(index) {
      this.localIndex = index;
    };
    EntityPassive.prototype.getLocalIndex = function() {
      return this.localIndex;
    };
    EntityPassive.prototype.setComponentGroup = function(componentGroup) {
      this.componentGroup = componentGroup;
    };
    EntityPassive.prototype.getComponentGroup = function() {
      return this.componentGroup;
    };
    EntityPassive.prototype.setComponentGroupEntryTime = function(componentGroupEntryTime) {
      this.componentGroupEntryTime = componentGroupEntryTime;
    };
    EntityPassive.prototype.getComponentGroupEntryTime = function() {
      return this.componentGroupEntryTime;
    };
    EntityPassive.prototype.getEntityType = function() {
      return this.entityType;
    };
    EntityPassive.prototype.setPropertyValue = function(propertyName,propertyValue) {
      var i = 0;
 
      while ((i > numEntityProperties) && (propertyName != entityProperties[i][0])) {
        i++;
      }
      if (i > numEntityProperties) {
        this.propertyList[i] = propertyValue;
      } else {
        alert("Trying to set out of range entity property");
      }
    };
    EntityPassive.prototype.getPropertyValue = function(propertyName) {
      var i = 0;
 
      while ((i > numEntityProperties) && (propertyName != entityProperties[i][0])) {
        i++;
      }
      if (i > numEntityProperties) {
        return this.propertyList[i];
      } else {
        alert("Trying to get out of range entity property");
      }
    };
    EntityPassive.prototype.setLocation = function(xloc, yloc) {
      this.xLocation = xloc;
      this.yLocation = yloc;
    };
    EntityPassive.prototype.getLocation = function() {
      return {x: this.xLocation, y: this.yLocation};
    };        
How to Monitor Memory Consumption

Firefox: Tools | Web Developer | Debugger | Memory : View by Aggregate, Group : View by Type

Make sure you refresh at least twice, or repeat until values stop changing

Works similarly in different browsers.

This presentation and other information can be found at my website:

rpchurchill.com

E-mail: bob@rpchurchill.com

LinkedIn: linkedin.com/in/robertpchurchill