Additional, optimized constructors & AddRange variants added to RecyclableArrayList. Benchmarks extended.

This commit is contained in:
mlemanczyk committed 2023-01-15 12:24:00 +01:00
1 parent 878ec4924a
commit c48b4e2f56
12 files changed
+486 -77

No files matched your search

@@ -14,6 +14,12 @@
protected object[]? _testObjects; protected object[]? _testObjects;
protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray(); protected object[] TestObjects => _testObjects ??= Enumerable.Range(1, TestObjectCount).Select(x => new object()).ToArray();
private List<object>? _testObjectsAsList;
protected List<object> TestObjectsAsList => _testObjectsAsList ??= TestObjects.ToList();
private RecyclableArrayList<object>? _testObjectsAsRecyclableArrayList;
protected RecyclableArrayList<object> TestObjectsAsRecyclableArrayList => _testObjectsAsRecyclableArrayList ??= TestObjects.ToRecyclableArrayList();
protected static void DoNothing<T>(T obj) protected static void DoNothing<T>(T obj)
{ {
_ = obj; _ = obj;
+2 -2
View File
@@ -3,8 +3,8 @@ using Recyclable.Collections.Benchmarks;
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" }); //BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(new[] { "-f*" });
BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>(); //BenchmarkRunner.Run<RecyclableCollectionsBenchmarks>();
//BenchmarkRunner.Run<RefVsInstanceMemberBenchmarks>(); BenchmarkRunner.Run<RefVsInstanceMemberBenchmarks>();
//var benchmark = new RecyclableCollectionsBenchmarks(); //var benchmark = new RecyclableCollectionsBenchmarks();
//benchmark.RecyclableArrayList_AddRange_WithCapacity(); //benchmark.RecyclableArrayList_AddRange_WithCapacity();
@@ -7,27 +7,17 @@ namespace Recyclable.Collections.Benchmarks
{ {
private readonly object[] _testArray; private readonly object[] _testArray;
//[Benchmark(Baseline = true)] [Benchmark(Baseline = false)]
public void Array_Create_WithCapacity() public void Array_Create_WithCapacity()
{ {
DoNothing(new object[TestObjectCount]); var data = new object[TestObjectCount];
DoNothing(data);
} }
//[Benchmark] [Benchmark(Baseline = true)]
public void Array_GetItem()
{
var data = _testArray;
var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++)
{
DoNothing(data[i]);
}
}
//[Benchmark]
public void Array_SetItem() public void Array_SetItem()
{ {
var data = _testArray; var data = TestObjects;
var dataCount = TestObjectCount; var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++) for (var i = 0; i < dataCount; i++)
{ {
@@ -35,14 +25,25 @@ namespace Recyclable.Collections.Benchmarks
} }
} }
//[Benchmark] [Benchmark]
public void Array_GetItem()
{
var data = TestObjects;
var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++)
{
DoNothing(data[i]);
}
}
[Benchmark]
public void Array_Count() public void Array_Count()
{ {
var data = _testArray; var data = _testArray;
DoNothing(data.Length); DoNothing(data.Length);
} }
//[Benchmark] [Benchmark]
public void Array_LongCount() public void Array_LongCount()
{ {
var data = _testArray; var data = _testArray;
@@ -6,7 +6,7 @@ namespace Recyclable.Collections.Benchmarks
{ {
private readonly List<object> _testList; private readonly List<object> _testList;
//[Benchmark(Baseline = true)] //[Benchmark(Baseline = false)]
public void List_Create() public void List_Create()
{ {
var list = new List<object>(); var list = new List<object>();
@@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks
DoNothing(list); DoNothing(list);
} }
//[Benchmark] [Benchmark]
public void List_Add() public void List_Add()
{ {
var data = TestObjects; var data = TestObjects;
@@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks
} }
} }
//[Benchmark(Baseline = true)] [Benchmark(Baseline = false)]
public void List_Add_WithCapacity() public void List_Add_WithCapacity()
{ {
var data = TestObjects; var data = TestObjects;
@@ -52,14 +52,38 @@ namespace Recyclable.Collections.Benchmarks
} }
[Benchmark] [Benchmark]
public void List_AddRange() public void List_AddRangeWhenSourceIsArray()
{ {
var data = TestObjects; var data = TestObjects;
var list = new List<object>(); var list = new List<object>();
list.AddRange(data); list.AddRange(data);
} }
[Benchmark(Baseline = true)] [Benchmark]
public void List_AddRangeWhenSourceIsList()
{
var data = TestObjectsAsList;
var list = new List<object>();
list.AddRange(data);
}
[Benchmark]
public void List_AddRangeWhenSourceIsIList()
{
var data = (IList<object>)TestObjectsAsList;
var list = new List<object>();
list.AddRange(data);
}
[Benchmark]
public void List_AddRangeWhenSourceIsIEnumerable()
{
var data = (IEnumerable<object>)TestObjects;
var list = new List<object>();
list.AddRange(data);
}
[Benchmark(Baseline = false)]
public void List_AddRange_WithCapacity() public void List_AddRange_WithCapacity()
{ {
var data = TestObjects; var data = TestObjects;
@@ -68,21 +92,10 @@ namespace Recyclable.Collections.Benchmarks
list.AddRange(data); list.AddRange(data);
} }
//[Benchmark] [Benchmark]
public void List_GetItem()
{
var data = _testList;
var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++)
{
DoNothing(data[i]);
}
}
//[Benchmark]
public void List_SetItem() public void List_SetItem()
{ {
var data = _testList; var data = TestObjectsAsList;
var dataCount = TestObjectCount; var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++) for (var i = 0; i < dataCount; i++)
{ {
@@ -90,7 +103,18 @@ namespace Recyclable.Collections.Benchmarks
} }
} }
//[Benchmark] [Benchmark]
public void List_GetItem()
{
var data = TestObjectsAsList;
var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++)
{
DoNothing(data[i]);
}
}
[Benchmark]
public void List_Count() public void List_Count()
{ {
var data = _testList; var data = _testList;
@@ -41,7 +41,7 @@ namespace Recyclable.Collections.Benchmarks
DoNothing(list); DoNothing(list);
} }
//[Benchmark] [Benchmark]
public void RecyclableArrayList_Add() public void RecyclableArrayList_Add()
{ {
var data = TestObjects; var data = TestObjects;
@@ -53,7 +53,7 @@ namespace Recyclable.Collections.Benchmarks
} }
} }
//[Benchmark] [Benchmark]
public void RecyclableArrayList_Add_WithCapacity() public void RecyclableArrayList_Add_WithCapacity()
{ {
var data = TestObjects; var data = TestObjects;
@@ -66,13 +66,45 @@ namespace Recyclable.Collections.Benchmarks
} }
[Benchmark] [Benchmark]
public void RecyclableArrayList_AddRange() public void RecyclableArrayList_AddRangeWhenSourceIsArray()
{ {
var data = TestObjects; var data = TestObjects;
using var list = new RecyclableArrayList<object>(); using var list = new RecyclableArrayList<object>();
list.AddRange(data); list.AddRange(data);
} }
[Benchmark]
public void RecyclableArrayList_AddRangeWhenSourceIsList()
{
var data = TestObjectsAsList;
using var list = new RecyclableArrayList<object>();
list.AddRange(data);
}
[Benchmark]
public void RecyclableArrayList_AddRangeWhenSourceIsIList()
{
var data = (IList<object>)TestObjectsAsList;
using var list = new RecyclableArrayList<object>();
list.AddRange(data);
}
[Benchmark]
public void RecyclableArrayList_AddRangeWhenSourceIsIEnumerable()
{
var data = (IEnumerable<object>)TestObjects;
using var list = new RecyclableArrayList<object>();
list.AddRange(data);
}
[Benchmark]
public void RecyclableArrayList_AddRangeWhenSourceIsRecyclableArrayList()
{
var data = TestObjectsAsRecyclableArrayList;
using var list = new RecyclableArrayList<object>();
list.AddRange(data);
}
[Benchmark] [Benchmark]
public void RecyclableArrayList_AddRange_WithCapacity() public void RecyclableArrayList_AddRange_WithCapacity()
{ {
@@ -82,10 +114,23 @@ namespace Recyclable.Collections.Benchmarks
list.AddRange(data); list.AddRange(data);
} }
//[Benchmark] [Benchmark]
public void RecyclableArrayList_SetItem()
{
var data = TestObjectsAsRecyclableArrayList;
var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++)
{
data[i] = TestObjects[i];
}
data.Count = dataCount;
}
[Benchmark]
public void RecyclableArrayList_GetItem() public void RecyclableArrayList_GetItem()
{ {
var data = _testRecyclableArrayList; var data = TestObjectsAsRecyclableArrayList;
var dataCount = TestObjectCount; var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++) for (var i = 0; i < dataCount; i++)
{ {
@@ -93,18 +138,7 @@ namespace Recyclable.Collections.Benchmarks
} }
} }
//[Benchmark] [Benchmark]
public void RecyclableArrayList_SetItem()
{
var data = _testRecyclableArrayList;
var dataCount = TestObjectCount;
for (var i = 0; i < dataCount; i++)
{
data[i] = TestObjects[i];
}
}
//[Benchmark]
public void RecyclableArrayList_Count() public void RecyclableArrayList_Count()
{ {
var data = _testRecyclableArrayList; var data = _testRecyclableArrayList;
@@ -27,7 +27,7 @@ namespace Recyclable.Collections.Benchmarks
DoNothing(list); DoNothing(list);
} }
[Benchmark] //[Benchmark]
public void RecyclableList_Add() public void RecyclableList_Add()
{ {
var data = TestObjects; var data = TestObjects;
@@ -39,7 +39,7 @@ namespace Recyclable.Collections.Benchmarks
} }
} }
[Benchmark] //[Benchmark]
public void RecyclableList_Add_WithCapacity() public void RecyclableList_Add_WithCapacity()
{ {
var data = TestObjects; var data = TestObjects;
@@ -18,14 +18,27 @@ namespace Recyclable.Collections.Benchmarks
private class TestCase private class TestCase
{ {
private int[] _items = new int[123];
private int _count = 123; private int _count = 123;
private int Add(int count)
{
return count;
}
public int Count public int Count
{ {
get => _count; get => _count;
set => _count = value; set => _count = value;
} }
public ref int GetRefCount() public ref int GetCountAsRefCount2()
{
ref int count = ref _count;
count = 234;
return ref count;
}
public ref int GetCountAsRefCount3()
{ {
ref int count = ref _count; ref int count = ref _count;
count = 234; count = 234;
@@ -33,30 +46,299 @@ namespace Recyclable.Collections.Benchmarks
return ref count; return ref count;
} }
private int Add(int count) public ref int GetCountAsRefCount4()
{ {
return count; ref int count = ref _count;
count = 234;
_ = Add(count);
_ = Add(count);
return ref count;
} }
public int GetCount() public ref int GetCountAsRefCount5()
{
ref int count = ref _count;
count = 234;
_ = Add(count);
_ = Add(count);
_ = Add(count);
return ref count;
}
public int GetCountAsInstanceMember2()
{
_count = 234;
return _count;
}
public int GetCountAsInstanceMember3()
{ {
_count = 234; _count = 234;
_ = Add(_count); _ = Add(_count);
return _count; return _count;
} }
public int GetCountAsInstanceMember4()
{
_count = 234;
_ = Add(_count);
_ = Add(_count);
return _count;
}
public int GetCountAsInstanceMember5()
{
_count = 234;
_ = Add(_count);
_ = Add(_count);
_ = Add(_count);
return _count;
}
public int GetLengthAsArray2()
{
_ = Add(_items.Length);
return _items.Length;
}
public int GetLengthAsArray3()
{
_ = Add(_items.Length);
_ = Add(_items.Length);
return _items.Length;
}
public int GetLengthAsArray4()
{
_ = Add(_items.Length);
_ = Add(_items.Length);
_ = Add(_items.Length);
return _items.Length;
}
public int GetLengthAsArray5()
{
_ = Add(_items.Length);
_ = Add(_items.Length);
_ = Add(_items.Length);
_ = Add(_items.Length);
return _items.Length;
}
public int GetLengthAsArray6()
{
_ = Add(_items.Length);
_ = Add(_items.Length);
_ = Add(_items.Length);
_ = Add(_items.Length);
_ = Add(_items.Length);
return _items.Length;
}
public int GetLengthAsArrayLocalVar5()
{
var items = _items;
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
return items.Length;
}
public int GetLengthAsArrayLocalVar6()
{
var items = _items;
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
return items.Length;
}
public int GetLengthAsRefArray2()
{
ref int[] items = ref _items;
_ = Add(items.Length);
return items.Length;
}
public int GetLengthAsRefArray3()
{
ref int[] items = ref _items;
_ = Add(items.Length);
_ = Add(items.Length);
return items.Length;
}
public int GetLengthAsRefArray4()
{
ref int[] items = ref _items;
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
return items.Length;
}
public int GetLengthAsRefArray5()
{
ref int[] items = ref _items;
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
return items.Length;
}
public int GetLengthAsRefArray6()
{
ref int[] items = ref _items;
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
_ = Add(items.Length);
return items.Length;
}
} }
[Benchmark] [Benchmark]
public void Ref() public void RefToInt2()
{ {
var count = _testCase.GetRefCount(); var count = _testCase.GetCountAsRefCount2();
DoNothing(count);
}
[Benchmark]
public void RefToInt3()
{
var count = _testCase.GetCountAsRefCount3();
DoNothing(count);
}
[Benchmark]
public void RefToInt4()
{
var count = _testCase.GetCountAsRefCount4();
DoNothing(count);
}
[Benchmark]
public void RefToInt5()
{
var count = _testCase.GetCountAsRefCount5();
DoNothing(count); DoNothing(count);
} }
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void InstanceMember() public void IntInstanceMember2()
{ {
var count = _testCase.GetCount(); var count = _testCase.GetCountAsInstanceMember2();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void IntInstanceMember3()
{
var count = _testCase.GetCountAsInstanceMember3();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void IntInstanceMember4()
{
var count = _testCase.GetCountAsInstanceMember4();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void IntInstanceMember5()
{
var count = _testCase.GetCountAsInstanceMember5();
DoNothing(count);
}
[Benchmark]
public void RefToArray2()
{
var count = _testCase.GetLengthAsRefArray2();
DoNothing(count);
}
[Benchmark]
public void RefToArray3()
{
var count = _testCase.GetLengthAsRefArray3();
DoNothing(count);
}
[Benchmark]
public void RefToArray4()
{
var count = _testCase.GetLengthAsRefArray4();
DoNothing(count);
}
[Benchmark]
public void RefToArray5()
{
var count = _testCase.GetLengthAsRefArray5();
DoNothing(count);
}
[Benchmark]
public void RefToArray6()
{
var count = _testCase.GetLengthAsRefArray6();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void ArrayInstanceMember2()
{
var count = _testCase.GetLengthAsArray2();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void ArrayInstanceMember3()
{
var count = _testCase.GetLengthAsArray3();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void ArrayInstanceMember4()
{
var count = _testCase.GetLengthAsArray4();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void ArrayInstanceMember5()
{
var count = _testCase.GetLengthAsArray5();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void ArrayInstanceMember6()
{
var count = _testCase.GetLengthAsArray6();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void ArrayInstanceMemberLocalVar5()
{
var count = _testCase.GetLengthAsArrayLocalVar5();
DoNothing(count);
}
[Benchmark(Baseline = false)]
public void ArrayInstanceMemberLocalVar6()
{
var count = _testCase.GetLengthAsArrayLocalVar6();
DoNothing(count); DoNothing(count);
} }
} }
+52 -5
View File
@@ -82,7 +82,6 @@ namespace Recyclable.Collections
public RecyclableArrayList(int initialCapacity) public RecyclableArrayList(int initialCapacity)
#pragma warning restore CS8618 #pragma warning restore CS8618
{ {
// _memory will be initialized when the 1st item is added
if (initialCapacity > 0) if (initialCapacity > 0)
{ {
_memory = SetNewLength(_memory, initialCapacity); _memory = SetNewLength(_memory, initialCapacity);
@@ -90,6 +89,34 @@ namespace Recyclable.Collections
} }
} }
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
public RecyclableArrayList(RecyclableArrayList<T> source)
#pragma warning restore CS8618
{
AddRange(source);
}
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
public RecyclableArrayList(in T[] source)
#pragma warning restore CS8618
{
AddRange(source);
}
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
public RecyclableArrayList(List<T> source)
#pragma warning restore CS8618
{
AddRange(source);
}
#pragma warning disable CS8618 // _memory will be initialized when the 1st item is added
public RecyclableArrayList(IList<T> source)
#pragma warning restore CS8618
{
AddRange(source);
}
public RecyclableArrayList(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.Capacity) public RecyclableArrayList(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.Capacity)
{ {
_memory = SetNewLength(_memory, initialCapacity); _memory = SetNewLength(_memory, initialCapacity);
@@ -126,7 +153,7 @@ namespace Recyclable.Collections
public bool IsReadOnly { get; } = false; public bool IsReadOnly { get; } = false;
public void Add(in T item) public void Add(T item)
{ {
int requestedCapacity = _count + 1; int requestedCapacity = _count + 1;
if (_capacity < requestedCapacity) if (_capacity < requestedCapacity)
@@ -152,7 +179,7 @@ namespace Recyclable.Collections
_count = targetCapacity; _count = targetCapacity;
} }
public void AddRange(in List<T> items) public void AddRange(List<T> items)
{ {
var sourceItemsCount = items.Count; var sourceItemsCount = items.Count;
var targetCapacity = _count + sourceItemsCount; var targetCapacity = _count + sourceItemsCount;
@@ -165,7 +192,20 @@ namespace Recyclable.Collections
_count = targetCapacity; _count = targetCapacity;
} }
public void AddRange(in RecyclableArrayList<T> items) public void AddRange(IList<T> items)
{
var sourceItemsCount = items.Count;
var targetCapacity = _count + sourceItemsCount;
if (_capacity < targetCapacity)
{
_ = EnsureCapacity(targetCapacity);
}
items.CopyTo(_memory, _count);
_count = targetCapacity;
}
public void AddRange(RecyclableArrayList<T> items)
{ {
var sourceItemsCount = items._count; var sourceItemsCount = items._count;
var targetCapacity = _count + sourceItemsCount; var targetCapacity = _count + sourceItemsCount;
@@ -180,10 +220,17 @@ namespace Recyclable.Collections
_count = targetCapacity; _count = targetCapacity;
} }
public void AddRange(IEnumerable<T> source) public void AddRange(IEnumerable<T> source, int growByCount = 100)
{ {
ref int capacity = ref _capacity;
ref int count = ref _count;
foreach (var item in source) foreach (var item in source)
{ {
if (capacity == count)
{
_ = EnsureCapacity(capacity * growByCount);
}
Add(item); Add(item);
} }
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+1 -1
View File
@@ -3,7 +3,7 @@
public class RecyclableDefaults public class RecyclableDefaults
{ {
public const int BlockSize = 10_240; public const int BlockSize = 10_240;
public const int Capacity = 8; public const int Capacity = 1;
public const int MinPooledArrayLength = 100; public const int MinPooledArrayLength = 100;
} }
} }
@@ -121,11 +121,26 @@ namespace Recyclable.Collections
return -1; return -1;
} }
//[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static RecyclableList<T> ToRecyclableList<T>(this IList<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); public static RecyclableList<T> ToRecyclableList<T>(this IList<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
//[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); public static RecyclableList<T> ToRecyclableList<T>(this List<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableArrayList<T> ToRecyclableArrayList<T>(this IEnumerable<T> values) => new(values, values.TryGetNonEnumeratedCount(out int count) ? count : RecyclableDefaults.Capacity);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableArrayList<T> ToRecyclableArrayList<T>(this T[] values) => new(values);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableArrayList<T> ToRecyclableArrayList<T>(this List<T> values) => new(values);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableArrayList<T> ToRecyclableArrayList<T>(this RecyclableArrayList<T> values) => new(values);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] RentArrayFromPool<T>(this int minSize, ArrayPool<T> arrayPool) => (minSize >= _minPooledArraySize) public static T[] RentArrayFromPool<T>(this int minSize, ArrayPool<T> arrayPool) => (minSize >= _minPooledArraySize)
@@ -161,7 +161,7 @@ namespace Recyclable.CollectionsTests
} }
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
_ = list.Capacity.Should().Be(32); _ = list.Capacity.Should().Be(20);
} }
[Fact] [Fact]
@@ -184,7 +184,7 @@ namespace Recyclable.CollectionsTests
} }
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
_ = list.Capacity.Should().Be(32); _ = list.Capacity.Should().Be(20);
} }
[Fact] [Fact]