Skip to content

Instantly share code, notes, and snippets.

@billywhizz
Last active August 21, 2024 20:14
Show Gist options
  • Save billywhizz/e776dc26e149f6720410d37590e9aff0 to your computer and use it in GitHub Desktop.
Save billywhizz/e776dc26e149f6720410d37590e9aff0 to your computer and use it in GitHub Desktop.

a good example of where dynamic/jit is good is sqlite api.

with dynamic/jit, we can compile a function that is specific to the table layout and column types of the query we are running. we can eliminate code this way.

if we implement this in C or some statically compiled language then we have to use the same logic for every query, which is less efficient.

e.g.

get () {
  const { types, names, stmt } = this
  if(this.step() === ROW) {
    const { columns } = this
    const row = {}
    for (let i = 0; i < columns; i++) {
      if (types[i] === 1) {
        row[names[i]] = column_int(stmt, i)
      } else if (types[i] === 2) {
        row[names[i]] = column_double(stmt, i)
      } else if (types[i] === 3) {
        row[names[i]] = this.columnText(i)
      } else if (types[i] === 4) {
        row[names[i]] = this.columnBlob(i)
      }
    }
    this.reset()
    return row
  }
}

this is the generic function that works for all queries.

but, if we know the types, which we pretty much always do with SQL, we can eliminate all the branching and produce this:

get () {
  const { types, names, stmt } = this
  const row = new foo_num()
  if (sqlite.step(stmt) === 100) {
    row['Id'] = sqlite.column_int(stmt, 0)
    row['CustomerId'] = this.columnText(1)
    row['EmployeeId'] = sqlite.column_int(stmt, 2)
    row['OrderDate'] = this.columnText(3)
    row['RequiredDate'] = this.columnText(4)
    row['ShippedDate'] = this.columnText(5)
    row['ShipVia'] = sqlite.column_int(stmt, 6)
    row['Freight'] = sqlite.column_double(stmt, 7)
    row['ShipName'] = this.columnText(8)
    row['ShipAddress'] = this.columnText(9)
    row['ShipCity'] = this.columnText(10)
    row['ShipRegion'] = this.columnText(11)
    row['ShipPostalCode'] = this.columnText(12)
    row['ShipCountry'] = this.columnText(13)
  }
  sqlite.reset(stmt)
  return row
}

this eliminates a loop through the resultset columns and from one to four indexes into the types array and another index into the names array inside the loop.

and this:

all () {
  const rows = []
  const { stmt } = this
  let rc = step(stmt)
  //let rc = this.step()
  let count = 0
  if (!this.types.length) {
    this.columns = column_count(stmt)
    for (let i = 0; i < this.columns; i++) {
      this.names.push(utf8Decode(column_name(stmt, i), -1))
      this.types.push(column_type(stmt, i))
    }
  }
  const { columns, names, types } = this
  while (rc === ROW) {
    const row = {}
    for (let i = 0; i < columns; i++) {
      // todo: these could be indexes into a function table
      if (types[i] === 1) {
        row[names[i]] = column_int(stmt, i)
      } else if (types[i] === 2) {
        row[names[i]] = column_double(stmt, i)
      } else if (types[i] === 3) {
        row[names[i]] = this.columnText(i)
      } else if (types[i] === 4) {
        row[names[i]] = this.columnBlob(i)
      }
    }
    rows.push(row)
    count++
    rc = step(stmt)
  }
  assert(rc === OK || rc === DONE)
  this.count = count
  this.reset()
  return rows
}

becomes this:

all () {
  const { types, names, cols, stmt } = this
  const rows = []
  let rc = sqlite.step(stmt)
  let i = 0
  while (rc === 100) {
    const row = new foo_num()
    row['Id'] = sqlite.column_int(stmt, 0)
    row['CustomerId'] = this.columnText(1)
    row['EmployeeId'] = sqlite.column_int(stmt, 2)
    row['OrderDate'] = this.columnText(3)
    row['RequiredDate'] = this.columnText(4)
    row['ShippedDate'] = this.columnText(5)
    row['ShipVia'] = sqlite.column_int(stmt, 6)
    row['Freight'] = sqlite.column_double(stmt, 7)
    row['ShipName'] = this.columnText(8)
    row['ShipAddress'] = this.columnText(9)
    row['ShipCity'] = this.columnText(10)
    row['ShipRegion'] = this.columnText(11)
    row['ShipPostalCode'] = this.columnText(12)
    row['ShipCountry'] = this.columnText(13)
    rows.push(row)
    rc = sqlite.step(stmt)
    i++
  }
  this.count = i
  sqlite.reset(stmt)
  return rows
}

this eliminates the same loop as the get method but in this case it is an inner loop when iterating through a result set for the all method. we also don't need to do the check for the column types on the first run of the statement. if these columns were not cached in the original all implementation, then we would have to pay the price of iterating them on every query but the dynamic method won't.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment