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