diff --git a/samples/csharp/common/MLModelEngine.cs b/samples/csharp/common/MLModelEngine.cs new file mode 100644 index 000000000..6b5864a07 --- /dev/null +++ b/samples/csharp/common/MLModelEngine.cs @@ -0,0 +1,76 @@ +using Microsoft.ML.Core.Data; +using Microsoft.ML.Runtime.Data; +using System.IO; +using Microsoft.ML; +//using Microsoft.Extensions.Configuration; + +namespace Common +{ + public class MLModelEngine + where TData : class + where TPrediction : class, new() + { + private readonly MLContext _mlContext; + private readonly ITransformer _model; + private readonly ObjectPool> _predictionEnginePool; + private readonly int _minPredictionEngineObjectsInPool; + private readonly int _maxPredictionEngineObjectsInPool; + + public int CurrentPredictionEnginePoolSize + { + get { return _predictionEnginePool.CurrentPoolSize; } + } + + //Constructor with modelFilePathName to load + public MLModelEngine(MLContext mlContext, string modelFilePathName, int minPredictionEngineObjectsInPool = 5, int maxPredictionEngineObjectsInPool = 1000) + { + _mlContext = mlContext; + + //Load the ProductSalesForecast model from the .ZIP file + using (var fileStream = File.OpenRead(modelFilePathName)) + { + _model = mlContext.Model.Load(fileStream); + } + + _minPredictionEngineObjectsInPool = minPredictionEngineObjectsInPool; + _maxPredictionEngineObjectsInPool = maxPredictionEngineObjectsInPool; + + //Create PredictionEngine Object Pool + _predictionEnginePool = CreatePredictionEngineObjectPool(); + } + + //Constructor with ITransformer model already created + public MLModelEngine(MLContext mlContext, ITransformer model, int minPredictionEngineObjectsInPool = 5, int maxPredictionEngineObjectsInPool = 1000) + { + _mlContext = mlContext; + _model = model; + _minPredictionEngineObjectsInPool = minPredictionEngineObjectsInPool; + _maxPredictionEngineObjectsInPool = maxPredictionEngineObjectsInPool; + + //Create PredictionEngine Object Pool + _predictionEnginePool = CreatePredictionEngineObjectPool(); + } + + private ObjectPool> CreatePredictionEngineObjectPool() + { + return new ObjectPool>(() => _model.MakePredictionFunction(_mlContext), + _minPredictionEngineObjectsInPool, + _maxPredictionEngineObjectsInPool); + } + + public TPrediction Predict(TData dataSample) + { + //Get PredictionEngine object from the Object Pool + PredictionFunction predictionEngine = _predictionEnginePool.GetObject(); + + //Predict + TPrediction prediction = predictionEngine.Predict(dataSample); + + //Release used PredictionEngine object into the Object Pool + _predictionEnginePool.PutObject(predictionEngine); + + return prediction; + } + + } +} diff --git a/samples/csharp/common/ObjectPool.cs b/samples/csharp/common/ObjectPool.cs new file mode 100644 index 000000000..028a2fa45 --- /dev/null +++ b/samples/csharp/common/ObjectPool.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Common +{ + public class ObjectPool + { + private ConcurrentBag _objects; + private Func _objectGenerator; + private int _maxPoolSize; + + public int CurrentPoolSize + { + get { return _objects.Count; } + } + + public ObjectPool(Func objectGenerator, int minPoolSize = 5, int maxPoolSize = 50000) + { + if (objectGenerator == null) throw new ArgumentNullException("objectGenerator"); + _objects = new ConcurrentBag(); + _objectGenerator = objectGenerator; + _maxPoolSize = maxPoolSize; + + //Measure total time of minimum objects creation + var watch = System.Diagnostics.Stopwatch.StartNew(); + + //Create minimum number of objects in pool + for (int i = 0; i < minPoolSize; i++) + { + _objects.Add(_objectGenerator()); + } + + //Stop measuring time + watch.Stop(); + long elapsedMs = watch.ElapsedMilliseconds; + } + + public T GetObject() + { + T item; + if (_objects.TryTake(out item)) + { + return item; + } + else + { + if(_objects.Count <= _maxPoolSize) + return _objectGenerator(); + else + throw new InvalidOperationException("MaxPoolSize reached"); + } + } + + public void PutObject(T item) + { + _objects.Add(item); + } + } +} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/eShopDashboardML.sln b/samples/csharp/end-to-end-apps/Regression-SalesForecast/eShopDashboardML.sln index 9ecf2232c..b7169a114 100644 --- a/samples/csharp/end-to-end-apps/Regression-SalesForecast/eShopDashboardML.sln +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/eShopDashboardML.sln @@ -21,6 +21,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestObjectPoolingConsoleApp", "src\TestObjectPoolingConsoleApp\TestObjectPoolingConsoleApp.csproj", "{CF3DE8C7-81D6-4B2B-A2F0-82D15701F10A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {F5DC33CF-35B3-45DD-A4A2-977DEA38060A}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5DC33CF-35B3-45DD-A4A2-977DEA38060A}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5DC33CF-35B3-45DD-A4A2-977DEA38060A}.Release|Any CPU.Build.0 = Release|Any CPU + {CF3DE8C7-81D6-4B2B-A2F0-82D15701F10A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF3DE8C7-81D6-4B2B-A2F0-82D15701F10A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF3DE8C7-81D6-4B2B-A2F0-82D15701F10A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF3DE8C7-81D6-4B2B-A2F0-82D15701F10A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -47,6 +53,7 @@ Global {29DB8569-F5D6-4190-9DF4-8D18CA0AABA8} = {F395612F-24C7-4666-90B2-62E417033B4B} {5AB1C510-FEF6-4930-AE05-D16AF802084D} = {F395612F-24C7-4666-90B2-62E417033B4B} {F5DC33CF-35B3-45DD-A4A2-977DEA38060A} = {B3AF01E5-D172-47F9-991E-A85504958F43} + {CF3DE8C7-81D6-4B2B-A2F0-82D15701F10A} = {F395612F-24C7-4666-90B2-62E417033B4B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1E47A71B-4F99-48EA-9267-DEE93B23BA31} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/CountryData.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/CountryData.cs new file mode 100644 index 000000000..3b0f130e2 --- /dev/null +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/CountryData.cs @@ -0,0 +1,39 @@ + +namespace TestObjectPoolingConsoleApp.DataStructures +{ + /// + /// This is the input to the trained model. + /// + public class CountryData + { + // next,country,year,month,max,min,std,count,sales,med,prev + public CountryData(string country, int year, int month, float max, float min, float std, int count, float sales, float med, float prev) + { + this.country = country; + + this.year = year; + this.month = month; + this.max = max; + this.min = min; + this.std = std; + this.count = count; + this.sales = sales; + this.med = med; + this.prev = prev; + } + + public float next; + + public string country; + + public float year; + public float month; + public float max; + public float min; + public float std; + public float count; + public float sales; + public float med; + public float prev; + } +} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/CountrySalesPrediction.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/CountrySalesPrediction.cs new file mode 100644 index 000000000..8d19cee6f --- /dev/null +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/CountrySalesPrediction.cs @@ -0,0 +1,13 @@ + +namespace TestObjectPoolingConsoleApp.DataStructures +{ + /// + /// This is the output of the scored model, the prediction. + /// + public class CountrySalesPrediction + { + // Below columns are produced by the model's predictor. + public float Score; + } + +} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/ProductData.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/ProductData.cs new file mode 100644 index 000000000..41c96aaee --- /dev/null +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/ProductData.cs @@ -0,0 +1,38 @@ + +namespace TestObjectPoolingConsoleApp.DataStructures +{ + /// + /// This is the input to the trained model. + /// + public class ProductData + { + // next,productId,year,month,units,avg,count,max,min,prev + public ProductData(string productId, int year, int month, float units, float avg, + int count, float max, float min, float prev) + { + this.productId = productId; + this.year = year; + this.month = month; + this.units = units; + this.avg = avg; + this.count = count; + this.max = max; + this.min = min; + this.prev = prev; + } + + public float next; + + public string productId; + + public float year; + public float month; + public float units; + public float avg; + public float count; + public float max; + public float min; + public float prev; + } + +} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/ProductUnitPrediction.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/ProductUnitPrediction.cs new file mode 100644 index 000000000..c8a55a6e3 --- /dev/null +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/DataStructures/ProductUnitPrediction.cs @@ -0,0 +1,13 @@ + +namespace TestObjectPoolingConsoleApp.DataStructures +{ + /// + /// This is the output of the scored model, the prediction. + /// + public class ProductUnitPrediction + { + // Below columns are produced by the model's predictor. + public float Score; + } + +} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/ModelFiles/country_month_fastTreeTweedie.zip b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/ModelFiles/country_month_fastTreeTweedie.zip new file mode 100644 index 000000000..78dd980fc Binary files /dev/null and b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/ModelFiles/country_month_fastTreeTweedie.zip differ diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/ModelFiles/product_month_fastTreeTweedie.zip b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/ModelFiles/product_month_fastTreeTweedie.zip new file mode 100644 index 000000000..5748cd106 Binary files /dev/null and b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/ModelFiles/product_month_fastTreeTweedie.zip differ diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/Program.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/Program.cs new file mode 100644 index 000000000..09eb02d87 --- /dev/null +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/Program.cs @@ -0,0 +1,74 @@ +using Common; +using Microsoft.ML; +using System; +using System.Threading; +using System.Threading.Tasks; + +using TestObjectPoolingConsoleApp.DataStructures; + +namespace TestObjectPoolingConsoleApp +{ + class Program + { + static void Main(string[] args) + { + CancellationTokenSource cts = new CancellationTokenSource(); + + // Create an opportunity for the user to cancel. + Task.Run(() => + { + if (Console.ReadKey().KeyChar == 'c' || Console.ReadKey().KeyChar == 'C') + cts.Cancel(); + }); + + MLContext mlContext = new MLContext(seed:1); + string modelFolder = $"Forecast/ModelFiles"; + string modelFilePathName = $"ModelFiles/country_month_fastTreeTweedie.zip"; + var countrySalesModel = new MLModelEngine(mlContext, + modelFilePathName, + minPredictionEngineObjectsInPool: 2); + + Console.WriteLine("Current number of objects in pool: {0:####.####}", countrySalesModel.CurrentPredictionEnginePoolSize); + + //Single Prediction + var singleCountrySample = new CountryData("Australia", 2017, 1, 477, 164, 2486, 9, 10345, 281, 1029); + var singleNextMonthPrediction = countrySalesModel.Predict(singleCountrySample); + + Console.WriteLine("Prediction: {0:####.####}", singleNextMonthPrediction.Score); + + // Create a high demand for the modelEngine objects. + Parallel.For(0, 1000000, (i, loopState) => + { + //Sample country data + //next,country,year,month,max,min,std,count,sales,med,prev + //4.23056080166201,Australia,2017,1,477.34,164.916,2486.1346772137,9,10345.71,281.7,1029.11 + + var countrySample = new CountryData("Australia", 2017, 1, 477, 164, 2486, 9, 10345, 281, i); + + // This is the bottleneck in our application. All threads in this loop + // must serialize their access to the static Console class. + Console.CursorLeft = 0; + var nextMonthPrediction = countrySalesModel.Predict(countrySample); + + Console.WriteLine("Prediction: {0:####.####}", nextMonthPrediction.Score); + Console.WriteLine("-----------------------------------------"); + Console.WriteLine("Current number of objects in pool: {0:####.####}", countrySalesModel.CurrentPredictionEnginePoolSize); + + if (cts.Token.IsCancellationRequested) + loopState.Stop(); + + }); + + Console.WriteLine("-----------------------------------------"); + Console.WriteLine("Current number of objects in pool: {0:####.####}", countrySalesModel.CurrentPredictionEnginePoolSize); + + + Console.WriteLine("Press the Enter key to exit."); + Console.ReadLine(); + cts.Dispose(); + } + + } + + +} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/TestObjectPoolingConsoleApp.csproj b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/TestObjectPoolingConsoleApp.csproj new file mode 100644 index 000000000..27978a8fd --- /dev/null +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/TestObjectPoolingConsoleApp/TestObjectPoolingConsoleApp.csproj @@ -0,0 +1,30 @@ + + + + Exe + netcoreapp2.1 + + + + + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/CountrySalesForecastController.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/CountrySalesForecastController.cs index 29ce29050..91310d0ee 100644 --- a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/CountrySalesForecastController.cs +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/CountrySalesForecastController.cs @@ -11,6 +11,8 @@ using Microsoft.ML.Runtime.Data; using Serilog; +using Common; + namespace eShopDashboard.Controllers { [Produces("application/json")] @@ -18,18 +20,17 @@ namespace eShopDashboard.Controllers public class CountrySalesForecastController : Controller { private readonly AppSettings appSettings; - private readonly PredictionFunction countrySalesPredFunction; + private readonly MLModelEngine countrySalesModel; private readonly ILogger logger; public CountrySalesForecastController(IOptionsSnapshot appSettings, - PredictionFunction countrySalesPredFunction, - ILogger logger - ) + MLModelEngine countrySalesModel, + ILogger logger) { this.appSettings = appSettings.Value; - // Get injected Country Sales Prediction function - this.countrySalesPredFunction = countrySalesPredFunction; + // Get injected Country Sales Model for scoring + this.countrySalesModel = countrySalesModel; this.logger = logger; } @@ -52,19 +53,8 @@ public IActionResult GetCountrySalesForecast(string country, CountrySalesPrediction nextMonthSalesForecast = null; - //Set the critical section if using Singleton for the PredictionFunction object - // - //lock(this.countrySalesPredFunction) - //{ - //Predict action (Measure Prediction function Singleton vs. Scoped) - nextMonthSalesForecast = this.countrySalesPredFunction.Predict(countrySample); - //} - // - // Note that if using Scoped instead of singleton in DI/IoC you can remove the critical section - // It depends if you want better performance in single Http calls (using singleton) - // versus better scalability ann global performance if you have many Http requests/threads - // since the critical section is a bottleneck reducing the execution to one thread for that particular Predict() mathod call - // + //Predict + nextMonthSalesForecast = this.countrySalesModel.Predict(countrySample); //Stop measuring time watch.Stop(); diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/ProductDemandForecastController.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/ProductDemandForecastController.cs index 8fc723912..b62e2ba8d 100644 --- a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/ProductDemandForecastController.cs +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Controllers/ProductDemandForecastController.cs @@ -10,6 +10,8 @@ using Microsoft.ML.Runtime.Data; using Serilog; +using Common; + namespace eShopDashboard.Controllers { [Produces("application/json")] @@ -17,16 +19,15 @@ namespace eShopDashboard.Controllers public class ProductDemandForecastController : Controller { private readonly AppSettings appSettings; - private readonly PredictionFunction productSalesPredFunction; + private readonly MLModelEngine productSalesModel; - public ProductDemandForecastController(IOptionsSnapshot appSettings, - PredictionFunction productSalesPredFunction - ) + public ProductDemandForecastController(IOptionsSnapshot appSettings, + MLModelEngine productSalesModel) { this.appSettings = appSettings.Value; - // Get injected Product Sales Prediction function - this.productSalesPredFunction = productSalesPredFunction; + // Get injected Product Sales Model for scoring + this.productSalesModel = productSalesModel; } [HttpGet] @@ -39,23 +40,11 @@ public IActionResult GetProductUnitDemandEstimation(string productId, { // Build product sample var inputExample = new ProductData(productId, year, month, units, avg, count, max, min, prev); - - + ProductUnitPrediction nextMonthUnitDemandEstimation = null; - //Set the critical section if using Singleton for the PredictionFunction object - // - //lock(this.productSalesPredFunction) - //{ - // Returns prediction - nextMonthUnitDemandEstimation = this.productSalesPredFunction.Predict(inputExample); - //} - // - // Note that if using Scoped instead of singleton in DI/IoC you can remove the critical section - // It depends if you want better performance in single Http calls (using singleton) - // versus better scalability ann global performance if you have many Http requests/threads - // since the critical section is a bottleneck reducing the execution to one thread for that particular Predict() mathod call - // + //Predict + nextMonthUnitDemandEstimation = this.productSalesModel.Predict(inputExample); return Ok(nextMonthUnitDemandEstimation.Score); } diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Forecast/CountrySalesModel.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Forecast/CountrySalesModel.cs deleted file mode 100644 index 002e40188..000000000 --- a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Forecast/CountrySalesModel.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.ML.Core.Data; -using Microsoft.ML.Runtime.Data; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -using Microsoft.ML.Legacy; -using Microsoft.ML.Runtime.Api; -using Microsoft.ML; -using Microsoft.Extensions.Configuration; - -namespace eShopDashboard.Forecast -{ - public class CountrySalesModel - { - private readonly MLContext _mlContext; - private readonly ITransformer _model; - - //MLContext is injected through DI/IoC - public CountrySalesModel(MLContext mlContext, IConfiguration configuration) - { - _mlContext = mlContext; - string modelFolder = configuration["ForecastModelsPath"]; - - //Load the ProductSalesForecast model from the .ZIP file - using (var fileStream = File.OpenRead($"{modelFolder}/country_month_fastTreeTweedie.zip")) - { - _model = mlContext.Model.Load(fileStream); - } - } - - /// - /// This function creates a prediction function from the loaded model. - /// - public PredictionFunction CreatePredictionFunction() - { - return _model.MakePredictionFunction(_mlContext); - } - - } -} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Forecast/ProductSalesModel.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Forecast/ProductSalesModel.cs deleted file mode 100644 index 124eaf2b2..000000000 --- a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Forecast/ProductSalesModel.cs +++ /dev/null @@ -1,36 +0,0 @@ - -using Microsoft.Extensions.Configuration; -using Microsoft.ML; -using Microsoft.ML.Core.Data; -using Microsoft.ML.Runtime.Data; -using System.IO; - -namespace eShopDashboard.Forecast -{ - public class ProductSalesModel - { - private readonly MLContext _mlContext; - private readonly ITransformer _model; - - //MLContext is injected through DI/IoC - public ProductSalesModel(MLContext mlContext, IConfiguration configuration) - { - _mlContext = mlContext; - string modelFolder = configuration["ForecastModelsPath"]; - - //Load the ProductSalesForecast model from the .ZIP file - using (var fileStream = File.OpenRead($"{modelFolder}/product_month_fastTreeTweedie.zip")) - { - _model = mlContext.Model.Load(fileStream); - } - } - - /// - /// This function creates a prediction engine from the loaded model. - /// - public PredictionFunction CreatePredictionFunction() - { - return _model.MakePredictionFunction(_mlContext); - } - } -} diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Startup.cs b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Startup.cs index 93516ca83..cd470f7f4 100644 --- a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Startup.cs +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/Startup.cs @@ -13,6 +13,8 @@ using Microsoft.ML.Runtime.Data; using Serilog; +using Common; + namespace eShopDashboard { public class Startup @@ -48,42 +50,22 @@ public void ConfigureServices(IServiceCollection services) return new MLContext(seed: 1); }); - // ProductSalesModel created as singleton for the whole ASP.NET Core app - // since it is threadsafe and models can be pretty large objects - services.AddSingleton(); - - // PredictionFunction for "ProductSales" created as scoped because it is not thread-safe - // Prediction Functions should be be re-used across calls because there are expensive initializations - // If set to be used as Singleton is very important to use critical sections "lock(predFunct)" in the code - // because the 'Predict()' method is not reentrant. - // - //services.AddSingleton>((ctx) => - services.AddScoped>((ctx) => + services.AddSingleton >((ctx) => { - //Create the Prediction Function object from its related model - var model = ctx.GetRequiredService(); - return model.CreatePredictionFunction(); + MLContext mlContext = ctx.GetRequiredService(); + string modelFolder = Configuration["ForecastModelsPath"]; + string modelFilePathName = $"{modelFolder}/product_month_fastTreeTweedie.zip"; + return new MLModelEngine(mlContext, modelFilePathName); }); - - // CountrySalesModel created as singleton for the whole ASP.NET Core app - // since it is threadsafe and models can be pretty large objects - services.AddSingleton(); - - // PredictionFunction for "CountrySales" created as scoped because it is not thread-safe - // Prediction Functions should be be re-used across calls because there are expensive initializations - // If set to be used as Singleton is very important to use critical sections "lock(predFunct" in the code - // because the 'Predict()' method is not reentrant. - // - //services.AddSingleton>((ctx) => - services.AddScoped>((ctx) => + services.AddSingleton>((ctx) => { - //Create the Prediction Function object from its related model - var model = ctx.GetRequiredService(); - return model.CreatePredictionFunction(); + MLContext mlContext = ctx.GetRequiredService(); + string modelFolder = Configuration["ForecastModelsPath"]; + string modelFilePathName = $"{modelFolder}/country_month_fastTreeTweedie.zip"; + return new MLModelEngine(mlContext, modelFilePathName, minPredictionEngineObjectsInPool:50); }); - services.Configure(Configuration.GetSection("CatalogSettings")); services.AddMvc(); diff --git a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/eShopDashboard.csproj b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/eShopDashboard.csproj index f1912a74d..b08e04f9a 100644 --- a/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/eShopDashboard.csproj +++ b/samples/csharp/end-to-end-apps/Regression-SalesForecast/src/eShopDashboard/eShopDashboard.csproj @@ -23,6 +23,7 @@ + @@ -32,6 +33,10 @@ + + + + Always diff --git a/samples/csharp/v0.8-All-Samples.sln b/samples/csharp/v0.8-All-Samples.sln index 4058bfc6f..80427983b 100644 --- a/samples/csharp/v0.8-All-Samples.sln +++ b/samples/csharp/v0.8-All-Samples.sln @@ -63,6 +63,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageClassification.Score", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProductRecommendation.Solution", "ProductRecommendation.Solution", "{7C3CF2B0-F386-438F-811D-1FAE9ECCFF04}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestObjectPoolingConsoleApp", "end-to-end-apps\Regression-SalesForecast\src\TestObjectPoolingConsoleApp\TestObjectPoolingConsoleApp.csproj", "{DFB295B3-CA56-4971-AB70-316441F6B70D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -191,6 +193,14 @@ Global {A7D316AC-3081-4D1A-9AAF-BC2D7D84F356}.Release|Any CPU.Build.0 = Release|Any CPU {A7D316AC-3081-4D1A-9AAF-BC2D7D84F356}.Release|x64.ActiveCfg = Release|Any CPU {A7D316AC-3081-4D1A-9AAF-BC2D7D84F356}.Release|x64.Build.0 = Release|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Debug|x64.ActiveCfg = Debug|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Debug|x64.Build.0 = Debug|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Release|Any CPU.Build.0 = Release|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Release|x64.ActiveCfg = Release|Any CPU + {DFB295B3-CA56-4971-AB70-316441F6B70D}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -212,6 +222,7 @@ Global {5655380C-1A3D-469E-9018-D4A23950F536} = {D447649D-768F-44E8-8ECA-C711CD882DBB} {DE291554-85C3-4868-B666-1D295EC78CAD} = {820E8AF2-A47D-4AB8-A4AF-5CDFF97EBCDF} {A7D316AC-3081-4D1A-9AAF-BC2D7D84F356} = {256EED4C-79DE-46E5-8673-0991C630C6BD} + {DFB295B3-CA56-4971-AB70-316441F6B70D} = {A56C7785-F74C-41F4-92C7-E98CB2287B90} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {98369941-33DD-450C-A410-B9A91C8CDE91}