如何有效地将大块细分为许多大小为 JavaScript 的 2 次幂的小块

How to efficiently subdivide large block into many small blocks sized power of 2 in JavaScript

建立

function allocate(bits) {
  if ((bits & (bits - 1)) != 0) {
      throw "Parameter is not a power of 2";
  }

  if (bits < 128 || bits > 4194304) {
      throw "Bits required out of range";
  }

  var startBinIndex = Math.log2(bits >> 7);
  var lastBin = BIN_OF_BINS.length - 1;

  for (var binIndex = startBinIndex; binIndex <= lastBin ; binIndex++) {
      var bin = BIN_OF_BINS[binIndex];

      //
      // We have found a bin that is not empty...
      //
      if (bin.length != 0) {
          //
          // Calculate amount of memory this bin takes up
          //
          var thisBinMemorySize = (128 << binIndex);
          var lastBinOfBinsIndex = bin.length - 1;
          var binBlock = bin[lastBinOfBinsIndex];
          var memoryAddress = binBlock.start;


          //
          // We are going to return this block
          //
          var allocatedMemoryBlock = {start : memoryAddress, count : 1};

          //
          // Before we return the above block, we need to remove the block if count is 1 otherwise decrease count and adjust memory start pointer by bin size
          //
          if (binBlock.count == 1) {
              bin.pop();
          }
          else {
              binBlock.count--;
              binBlock.start += thisBinMemorySize;
          }

          //
          // if we want 1024 bits and it takes it from bin 15, we simply subtract 1024 from 4194304 which gives us 4193280
          // if we then populate bin 3 (1024 bits) onward, until bin 14, the exact number we end up populating those bins with is 4183280
          //
          var remainingUnsedMemory = thisBinMemorySize - bits;
          var adjustmentSize = bits;
          while (remainingUnsedMemory != 0) {
              memoryAddress += adjustmentSize;

              BIN_OF_BINS[startBinIndex].push({start : memoryAddress, count : 1});
              startBinIndex++;
              remainingUnsedMemory -= bits;
              adjustmentSize = bits;
              bits <<= 1;
          }

          return allocatedMemoryBlock;
      }
  }
  return null; // out of memory...
}

let BIN_OF_BINS = [
  [], // 128 bits each chunk
  [], // 256
  [], // 512
  [], // 1024
  [], // 2048
  [], // 4096
  [], // 8192
  [], // 16384
  [], // 32768
  [], // 65536
  [], // 131072
  [], // 262144
  [], // 524288
  [], // 1048576
  [], // 2097152
  [{ start: 0, count: 100 }], // 4194304
]

console.log("Memory returned:", allocate((128 << 1)));
console.log("Memory returned:", allocate((128 << 1)));
console.log("Memory returned:", allocate((128 << 1)));
console.log("Memory returned:", allocate((128 << 2)));
console.log("Memory returned:", allocate((128 << 2)));
console.log("Memory returned:", allocate((128 << 2)));
console.log("Memory returned:", allocate((128 << 1)));
console.log(JSON.stringify(BIN_OF_BINS, null, 2));

你怎么能让它接受两个参数,allocate(bits, count),而不是一个,其中 count 参数告诉要分配多少 bits 大小的内存插槽 提前?

我整晚都在尝试,但没有得到任何非常接近工作的结果,而且它开始失去原来答案的简单性。

// desired count is a power of two only
function allocateBunch(bins, base, size, desiredCount) {
  let desiredFactor = (2 ** (size - 1))
  let totalFound = 0
  let i = 0
  let n = bins.length
  while (i < n) {
    // 1024 4096
    let factor = 1 << i
    let bin = bins[i]
    while (bin.length) {
      block = bin[0]
      let diff = desiredCount - totalFound
      let diff1 = Math.ceil(diff / factor)
      let min = Math.min(block.count, diff1)
      block.count -= min
      totalFound += (min * factor)
      base.push({
        start: block.start,
        count: diff
      })
      block.start += ((min * factor) * (64 << size))
      let done = totalFound >= desiredCount
      if (done) return
      if (done) {
        var bits = (64 << size)
        var remainingUnsedMemory = ((min * factor) - diff) * bits
        var adjustmentSize = bits
        var startBinIndex = i
        var memoryAddress = block.start + (diff * bits)
        while (remainingUnsedMemory != 0) {
          // memoryAddress += adjustmentSize;

          // bins[startBinIndex].push({
          //   start: memoryAddress,
          //   count: 1
          // });
          // startBinIndex--;
          remainingUnsedMemory -= bits;
          // adjustmentSize = bits;
          // bits >>= 1;
        }
      } else {
        bin.shift()
      }
    }
    i++
  }
}

let BIN_OF_BINS = [
  [], // 128 bits each chunk
  [], // 256
  [], // 512
  [], // 1024
  [], // 2048
  [], // 4096
  [], // 8192
  [], // 16384
  [], // 32768
  [], // 65536
  [], // 131072
  [], // 262144
  [], // 524288
  [], // 1048576
  [], // 2097152
  [{ start: 0, count: 100 }], // 4194304
]

let base = []
allocateBunch(BIN_OF_BINS, base, 1, 4096)
allocateBunch(BIN_OF_BINS, base, 1, 8192)
console.log(BIN_OF_BINS, base)

基本上我要做的就是这个。假设您想提前分配 100、1000 或 4096(选择一些数字)块(遵循上下文的链接问题,或上面的第一个算法)。你有 BIN_OF_BINS ,它以一个顶级值开始,表示一些可用内存。您可以根据需要对其进行细分以获得所需的块,但实际上不需要递归细分内容,您可以使用快捷方式。你可以做一些计算来判断跳多远。但是很难到达那里。

如果块(在许多 additions/removals 或 allocate/frees 之后)散落在各处,理想情况下算法仍然能够收集一定大小的一堆块,因此它们已准备好采摘。

例如:如果我要求 1 个大小为 512 的块,我应该得到:

[
  [],
  [],
  [ { start: 0, count: 1 } ],
  [ { start: 1024, count: 1 } ],
  [ { start: 2048, count: 1 } ],
  [ { start: 4096, count: 1 } ],
  [ { start: 8192, count: 1 } ],
  [ { start: 16384, count: 1 } ],
  [ { start: 32768, count: 1 } ],
  [ { start: 65536, count: 1 } ],
  [ { start: 131072, count: 1 } ],
  [ { start: 262144, count: 1 } ],
  [ { start: 524288, count: 1 } ],
  [ { start: 1048576, count: 1 } ],
  [ { start: 2097152, count: 1 } ],
  [ { start: 4194304, count: 99 } ]
]

如果我改为请求 8192 512 位值,我应该:

[
  [],
  [],
  [ { start: 0, count: 8192 } ],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [],
  [ { start: 2097664, count: 1 } ],
  [ { start: 4194304, count: 99 } ]
]

更新版本,内存偏移计算现在是向后而不是向前。

let FRAGMENTED_BINS_OF_BIN = [
    [], // 128 bits each chunk
    [], // 256
    [], // 512
    [], // 1024
    [], // 2048
    [], // 4096
    [], // 8192
    [], // 16384
    [], // 32768
    [], // 65536
    [], // 131072
    [], // 262144
    [], // 524288
    [], // 1048576
    [], // 2097152
    [{ start: 0, count: 100 }], // 4194304
  ];
  
  function allocate(binDataSource, bits, quantity) {
    var requiredSize = bits * quantity
    if (requiredSize < 128 || requiredSize > 4194304) {
        throw "Bits required out of range";
    }
  
    var lastBin = binDataSource.length - 1;
  
  
    for (var binIndex = 0; binIndex <= lastBin ; binIndex++) {
        var bin = binDataSource[binIndex];
  
        //
        // We have found a bin that is not empty...
        //
        if (bin.length != 0) {
            //
            // Calculate amount of memory this bin takes up
            //
            var thisBinMemorySize = (128 << binIndex);
  
  
            for (var b = bin.length - 1; b >= 0; b--) {
                var blockOfInterest = bin[b];
                var blockSize = blockOfInterest.count * thisBinMemorySize;
                //
                // We've found a continous block this bin that fits the amount we want
                //
                if (blockSize >= requiredSize) {
                    //
                    // We are going to return this block
                    //
                    var allocatedMemoryBlock = {start : blockOfInterest.start, count : quantity, bits : bits};
  
                    //
                    // Figure out how many blocks were consumed...
                    //
                    var blockConsumed = Math.ceil(requiredSize / thisBinMemorySize);
                    var totalMemoryConsumed = thisBinMemorySize * blockConsumed;
                    //
                    // Perfect amount, so delete whole block from bin
                    //
                    if (blockConsumed == blockOfInterest.count) {
                        bin.splice(b);
                    }
                    else {
                        //
                        // Otherwise count reduced by consumed block amount and the start address is adjusted to account for consumed block
                        //
                        blockOfInterest.count -= blockConsumed;
                        blockOfInterest.start += thisBinMemorySize * blockConsumed;
                    }
  
                    //
                    // We may have acquired more memory than we need so we need to put the excess back into their respective bins...
                    //
                    var leftOverMemory = totalMemoryConsumed - requiredSize;
                    if (leftOverMemory > 0) {
                        //
                        // adjust the left over memory start address by the amount we have actually used
                        //
                        var leftOverMemoryAddressStartsAt = allocatedMemoryBlock.start + requiredSize;
                        var endOfMemory = allocatedMemoryBlock.start + totalMemoryConsumed;
  
                        while (leftOverMemory != 0) {
                            //
                            // Previous bin... size and index
                            //
                            thisBinMemorySize >>= 1;
                            binIndex--;
  
                            //
                            // This unused block fits in this bin...
                            //
                            if (leftOverMemory - thisBinMemorySize >= 0) {
                                //
                                // Register it...
                                //
                                endOfMemory -= thisBinMemorySize;
                                binDataSource[binIndex].push({start : endOfMemory, count : 1});
  
                                //
                                // Reduce the amount of left over memory...
                                //
                                leftOverMemory -= thisBinMemorySize;
                                leftOverMemoryAddressStartsAt += thisBinMemorySize;
                            }
                        }
                    }
                    return allocatedMemoryBlock;
                }
            }
        }
    }
    return false; // failed to find any sizable blocks
  }
  
  function freeMemoryBlock(memoryBlock) {
    var bits = memoryBlock.bits;
  
    delete memoryBlock.bits;
  
    var belongsToBin = Math.log2(bits >> 7);
  
    FRAGMENTED_BINS_OF_BIN[belongsToBin].push(memoryBlock);
  }