Commit e06768d8 authored by Marc Gravell's avatar Marc Gravell

reduce allocations by pre-boxing the keys and interning the literals as pre-encoded chunks

parent 9c98b5e3
......@@ -8,9 +8,15 @@ namespace NRediSearch
{
public sealed class Client
{
internal static readonly LiteralCache Literals = new LiteralCache();
[Flags]
public enum IndexOptions
{
/// <summary>
/// All options disabled
/// </summary>
None = 0,
/// <summary>
/// Set this to tell the index not to save term offset vectors. This reduces memory consumption but does not
/// allow performing exact matches, and reduces overall relevance of multi-term queries
......@@ -35,23 +41,24 @@ private static void SerializeRedisArgs(IndexOptions flags, List<object> args)
{
if ((flags & IndexOptions.UseTermOffsets) == 0)
{
args.Add("NOOFFSETS");
args.Add(Literals["NOOFFSETS"]);
}
if ((flags & IndexOptions.KeepFieldFlags) == 0)
{
args.Add("NOFIELDS");
args.Add(Literals["NOFIELDS"]);
}
if ((flags & IndexOptions.UseScoreIndexes) == 0)
{
args.Add("NOSCOREIDX");
args.Add(Literals["NOSCOREIDX"]);
}
}
IDatabase _db;
public RedisKey IndexName { get; }
private object _boxedIndexName;
public RedisKey IndexName => (RedisKey)_boxedIndexName;
public Client(RedisKey indexName, IDatabase db)
{
_db = db;
IndexName = indexName;
_boxedIndexName = indexName; // only box once, not per-command
}
/// <summary>
......@@ -64,9 +71,9 @@ public bool CreateIndex(Schema schema, IndexOptions options)
{
var args = new List<object>();
args.Add(IndexName);
args.Add(_boxedIndexName);
SerializeRedisArgs(options, args);
args.Add("SCHEMA");
args.Add(Literals["SCHEMA"]);
foreach (var f in schema.Fields)
{
......@@ -84,7 +91,7 @@ public bool CreateIndex(Schema schema, IndexOptions options)
public SearchResult Search(Query q)
{
var args = new List<object>();
args.Add(IndexName);
args.Add(_boxedIndexName);
q.SerializeRedisArgs(args);
var resp = (RedisResult[])_db.Execute("FT.SEARCH", args.ToArray());
......@@ -102,23 +109,23 @@ public SearchResult Search(Query q)
/// <param name="payload">if set, we can save a payload in the index to be retrieved or evaluated by scoring functions on the server</param>
public bool AddDocument(string docId, double score, Dictionary<string, RedisValue> fields, bool noSave, bool replace, byte[] payload)
{
var args = new List<object> { IndexName, docId, score };
var args = new List<object> { _boxedIndexName, docId, score };
if (noSave)
{
args.Add("NOSAVE");
args.Add(Literals["NOSAVE"]);
}
if (replace)
{
args.Add("REPLACE");
args.Add(Literals["REPLACE"]);
}
if (payload != null)
{
args.Add("PAYLOAD");
args.Add(Literals["PAYLOAD"]);
// TODO: Fix this
args.Add(payload);
}
args.Add("FIELDS");
args.Add(Literals["FIELDS"]);
foreach (var ent in fields)
{
args.Add(ent.Key);
......@@ -150,11 +157,11 @@ public bool AddDocument(string docId, Dictionary<string, RedisValue> fields)
/// <returns>true on success</returns>
public bool AddHash(string docId, double score, bool replace)
{
var args = new List<object> { IndexName, docId, score };
var args = new List<object> { _boxedIndexName, docId, score };
if (replace)
{
args.Add("REPLACE");
args.Add(Literals["REPLACE"]);
}
return (string)_db.Execute("FT.ADDHASH", args.ToArray()) == "OK";
......@@ -168,7 +175,7 @@ public bool AddHash(string docId, double score, bool replace)
public Dictionary<string, RedisValue> GetInfo()
{
var res = (RedisValue[])_db.Execute("FT.INFO", IndexName);
var res = (RedisValue[])_db.Execute("FT.INFO", _boxedIndexName);
var info = new Dictionary<string, RedisValue>();
for (int i = 0; i < res.Length; i += 2)
{
......@@ -186,7 +193,7 @@ public bool AddHash(string docId, double score, bool replace)
/// <returns>true if it has been deleted, false if it did not exist</returns>
public bool DeleteDocument(string docId)
{
long r = (long)_db.Execute("FT.DEL", IndexName, docId);
long r = (long)_db.Execute("FT.DEL", _boxedIndexName, docId);
return r == 1;
}
......@@ -196,7 +203,7 @@ public bool DeleteDocument(string docId)
/// <returns>true on success</returns>
public bool DropIndex()
{
return (string)_db.Execute("FT.DROP", IndexName) == "OK";
return (string)_db.Execute("FT.DROP", _boxedIndexName) == "OK";
}
/// <summary>
......@@ -204,7 +211,7 @@ public bool DropIndex()
/// </summary>
public long OptimizeIndex()
{
long ret = (long)_db.Execute("FT.OPTIMIZE", IndexName);
long ret = (long)_db.Execute("FT.OPTIMIZE", _boxedIndexName);
return ret;
}
}
......
using StackExchange.Redis;
using System.Collections;
namespace NRediSearch
{
// encode and box literals once only
internal sealed class LiteralCache
{
private static Hashtable _boxed = new Hashtable();
public object this[string value]
{
get
{
if (value == null) return "";
object boxed = _boxed[value];
if (boxed == null)
{
lock (_boxed)
{
boxed = _boxed[value];
if (boxed == null)
{
boxed = (RedisValue)value;
_boxed.Add(value, boxed);
}
}
}
return boxed;
}
}
}
}
......@@ -60,7 +60,7 @@ RedisValue FormatNum(double num, bool exclude)
// need to add leading bracket
return "(" + num.ToString("G17", NumberFormatInfo.InvariantInfo);
}
args.Add("FILTER");
args.Add(Client.Literals["FILTER"]);
args.Add(Property);
args.Add(FormatNum(min, exclusiveMin));
args.Add(FormatNum(max, exclusiveMax));
......@@ -86,7 +86,7 @@ public GeoFilter(string property, double lon, double lat, double radius, GeoUnit
internal override void SerializeRedisArgs(List<object> args)
{
args.Add("GEOFILTER");
args.Add(Client.Literals["GEOFILTER"]);
args.Add(Property);
args.Add(lon);
args.Add(lat);
......@@ -94,10 +94,10 @@ internal override void SerializeRedisArgs(List<object> args)
switch (unit)
{
case GeoUnit.Feet: args.Add("ft"); break;
case GeoUnit.Kilometers: args.Add("km"); break;
case GeoUnit.Meters: args.Add("m"); break;
case GeoUnit.Miles: args.Add("mi"); break;
case GeoUnit.Feet: args.Add(Client.Literals["ft"]); break;
case GeoUnit.Kilometers: args.Add(Client.Literals["km"]); break;
case GeoUnit.Meters: args.Add(Client.Literals["m"]); break;
case GeoUnit.Miles: args.Add(Client.Literals["mi"]); break;
default: throw new InvalidOperationException($"Unknown unit: {unit}");
}
}
......@@ -175,45 +175,45 @@ internal void SerializeRedisArgs(List<object> args)
if (Verbatim)
{
args.Add("VERBATIM");
args.Add(Client.Literals["VERBATIM"]);
}
if (NoContent)
{
args.Add("NOCONTENT");
args.Add(Client.Literals["NOCONTENT"]);
}
if (NoStopwords)
{
args.Add("NOSTOPWORDS");
args.Add(Client.Literals["NOSTOPWORDS"]);
}
if (WithScores)
{
args.Add("WITHSCORES");
args.Add(Client.Literals["WITHSCORES"]);
}
if (WithPayloads)
{
args.Add("WITHPAYLOADS");
args.Add(Client.Literals["WITHPAYLOADS"]);
}
if (Language != null)
{
args.Add("LANGUAGE");
args.Add(Client.Literals["LANGUAGE"]);
args.Add(Language);
}
if (_fields != null && _fields.Length > 0)
{
args.Add("INFIELDS");
args.Add(Client.Literals["INFIELDS"]);
args.Add(_fields.Length);
args.AddRange(_fields);
}
if (Payload != null)
{
args.Add("PAYLOAD");
args.Add(Client.Literals["PAYLOAD"]);
args.Add(Payload);
}
if (_paging.Offset != 0 || _paging.Count != 10)
{
args.Add("LIMIT");
args.Add(Client.Literals["LIMIT"]);
args.Add(_paging.Offset);
args.Add(_paging.Count);
}
......
......@@ -31,13 +31,13 @@ internal Field(string name, FieldType type)
internal virtual void SerializeRedisArgs(List<object> args)
{
string GetForRedis(FieldType type)
object GetForRedis(FieldType type)
{
switch (type)
{
case FieldType.FullText: return "TEXT";
case FieldType.Geo: return "GEO";
case FieldType.Numeric: return "NUMERIC";
case FieldType.FullText: return Client.Literals["TEXT"];
case FieldType.Geo: return Client.Literals["GEO"];
case FieldType.Numeric: return Client.Literals["NUMERIC"];
default: throw new ArgumentOutOfRangeException(nameof(type));
}
}
......@@ -57,7 +57,7 @@ internal override void SerializeRedisArgs(List<object> args)
base.SerializeRedisArgs(args);
if (Weight != 1.0)
{
args.Add("WEIGHT");
args.Add(Client.Literals["WEIGHT"]);
args.Add(Weight);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment