【问题标题】:hash intersection possible with redis+lua?与redis + lua可能的哈希交集?
【发布时间】:2013-11-17 02:10:15
【问题描述】:

我有一个用例,我需要计算大量集合之间的相似性以创建一个简单的推荐引擎。我正在查看 Jaccard 系数和其他相似系数公式,但它们之间有一个共同点:集合内的项目不能重复(如果我在这里错了,请纠正我)。

我在 PHP 中编写了自己的函数来执行自定义哈希交集,其逻辑如下:

  1. arr1:一个数组,key是item的id,value是对应的数量。 这代表了用户的愿望清单。
  2. arr2:与 arr1 相同,但它代表另一个用户的库存。
  3. 我需要这个自定义交集的逻辑是愿望清单的所有者不关心卖家是否有 100 个 item1。如果他只想要 4 个,则只计算 4 个。

我需要一种非常快速的方法来使集合相交,但通常的相似系数公式涉及集合的相交和并集,当将一个集合与 200k 其他集合进行比较时,这可能不如我想要的那么快。这是我目前所处的位置:

function my_similarity_coefficient ($arr1, $arr2) {

    $matches = 0;
    $total = 0;

    if (count($arr2) == 0)
        return 0;

    foreach ($arr1 as $id => $qty) {

        $total += $qty;

        if (!array_key_exists($id, $arr2))
            continue;

        $matches += min($qty, $arr2[$id]); // do not match more than what user wants

    }

    return $matches / $total;

}

我尝试在 PHP 中交叉两个红色散列。大小分别为arr1[67] 和arr2[231]。该系数是在出色的 61.98 微秒(最坏情况下高达 266.075 微秒)下计算得出的。如果我尝试将数据从 Redis 获取到 PHP,这个数字会膨胀到 905.037µsec-3337.86µsec。

我想让瓶颈远离将数据从 redis 传输到 PHP,所以我想知道是否可以在 lua(甚至可能是 c++)中编写这个自定义交集,如果可以,它不会受到影响来自相同的瓶颈,因为它也是从 pointA 到 pointB 获取数据,还是不会因为数据已经是本地数据而遭受获取瓶颈?

我对 lua 不熟悉,但我不希望被准确的代码提供。由于互联网上很少有与我真正想要实现的目标相关的 lua 资源,所以我想在搜索时先在这里挑选一些大脑。

【问题讨论】:

    标签: lua redis lua-table


    【解决方案1】:

    让我们看看。首先,这是您的 PHP 代码直接翻译成 Lua。我在这里保留了相同的变量名,但你在 PHP 中所说的“数组”在 Lua 中被称为“表”。

    local my_similarity_coefficient = function(arr1, arr2)
    
      local matches = 0
      local total = 0
    
      if next(arr2) == nil then
        return 0
      end
    
      for id, qty in pairs(arr1) do
    
        total = total + qty
    
        if arr2[id] then
          matches = matches + math.min(qty, arr2[id])
        end
    
      end
    
      return matches / total
    
    end
    

    请注意,如果arr1 为空,则此代码可以除以零,但您的代码也可以。

    让我们试试吧:

    local arr1 = {
      a = 3,
      b = 5,
      c = 8,
    }
    
    local arr2 = {
      a = 2,
      c = 10,
      d = 7,
      e = 21,
    }
    
    print(my_similarity_coefficient(arr1, arr2)) -- 0.625
    

    现在让我们使用 Redis。首先,让我们创建测试数据。

    redis 127.0.0.1:6379> hmset arr1 a 3 b 5 c 8
    OK
    redis 127.0.0.1:6379> hmset arr2 a 2 c 10 d 7 e 21
    OK
    

    这个脚本做你想做的事,不是以最有效的方式(对redis.call的调用可能更少),而是以一种简单的方式,所以你可以理解它并在需要时对其进行优化:

    local k1, k2 = KEYS[1], KEYS[2]
    local matches, total = 0, 0
    
    if not redis.call("exists", k2) then return 0 end
    
    local qty, qty2
    for _, id in ipairs(redis.call("hkeys", k1)) do
      qty = tonumber(redis.call("hget", k1, id))
      total = total + qty
      qty2 = tonumber(redis.call("hget", k2, id) or 0)
      matches = matches + math.min(qty, qty2)
    end
    
    return tostring(matches / total)
    

    我们称之为:

    $ redis-cli eval "$(cat the_script.lua)" 2 arr1 arr2
    "0.625"
    

    成功了!

    需要注意的重要一点是类型转换:值(数量)通过tonumber 转换为整数(Redis 返回字符串),我们将结果转换为字符串,因为如果我们返回浮点数,Redis 会将其截断为整数(此处为 0)。

    编辑 - 好的,谈论优化而不是说如何不好,所以这是一个简单的:

    local k1, k2 = KEYS[1], KEYS[2]
    local matches, total = 0, 0
    
    if not redis.call("exists", k2) then return 0 end
    
    local t1 = redis.call("hgetall", k1)
    
    local id, qty, qty2
    for i=1,#t1,2 do
      id, qty = t1[i], tonumber(t1[i+1])
      total = total + qty
      qty2 = tonumber(redis.call("hget", k2, id) or 0)
      matches = matches + math.min(qty, qty2)
    end
    
    return tostring(matches / total)
    

    【讨论】:

    • 看起来不错。看来您已经为我完成了这项工作。是否有可能在一次旅行中完成一张桌子?
    • 你是说hgetall k2?只有当两个文档之间有很多交叉点时,这才是一个好主意。此外,hgetall 返回{key1, val1, key2, val2} 而不是{key1 = val1, key2 = val2},因此您必须进行转换。当前代码只执行一次网络传输,但 O(n) Lua - C 在 Redis 内部传输,因此它可能更快,也可能更快。所以总结一下:这是可能的,但你应该对其进行基准测试。
    • 好的,非常感谢!如果我可以投票两次,我会的! :)
    • 结果真的很奇怪。我进行了 1:40k 的运行:第一个从 redis 中一个一个地取出所有 arr2 并在原生 PHP 中进行系数计算。另一种是每次迭代调用 $redis->eval() 。原生 PHP = 16s PHP lua eval = 21s 并且得分一致。你认为我可能做错了什么吗?或者这是意料之中的?
    • 好吧,我想我终于搞定了。这仅在 3 秒内运行:pastebin.com/B1ALJanR 我认为这非常好。除非你能看到我可以优化它的地方。 :)
    猜你喜欢
    • 2012-11-13
    • 1970-01-01
    • 1970-01-01
    • 2013-03-24
    • 2011-06-16
    • 2010-11-30
    • 2020-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多