diff --git a/Gemfile b/Gemfile index 0b8e97a..8ab5894 100644 --- a/Gemfile +++ b/Gemfile @@ -4,3 +4,4 @@ gem "async" gem "falcon", "0.44.0" # use different version on your own risk; 0.44.0 worked gem "sinatra" gem "faraday" +gem "rspec" diff --git a/Gemfile.lock b/Gemfile.lock index 4090b46..ccd2a4e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -30,6 +30,7 @@ GEM fiber-annotation fiber-local (~> 1.1) json + diff-lcs (1.6.0) falcon (0.44.0) async async-container (~> 0.17) @@ -79,6 +80,19 @@ GEM rack (>= 3.0.0, < 4) rack-session (2.0.0) rack (>= 3.0.0) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.3) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.2) ruby2_keywords (0.0.5) samovar (2.3.0) console (~> 1.0) @@ -102,6 +116,7 @@ DEPENDENCIES async falcon (= 0.44.0) faraday + rspec sinatra BUNDLED WITH diff --git a/case_study.md b/case_study.md new file mode 100644 index 0000000..5f5dbc5 --- /dev/null +++ b/case_study.md @@ -0,0 +1,17 @@ +# Case-study оптимизации + +## Актуальная проблема +Есть программа, корректно выполняющая свою работу, но очень медленно. + +## Формирование метрики +Время выполнения программы должно укладываться в 7 секунд + +## Гарантия корректности работы оптимизированной программы +Был написан тест на корректность работы программы, который проверяет, что программа работает корректно и после оптимизации. + +## Вникаем в детали +В программе происходит много долгих запросов к серверу, которые можно выполнять параллельно. +Было принято решение использовать socketry/async для решения задачи. + +## Результаты +В результате проделанной оптимизации удалось улучшить метрику системы с ~19 секунд до ~6 и уложиться в заданный бюджет. diff --git a/client.rb b/client.rb index e001ba3..49e97f4 100644 --- a/client.rb +++ b/client.rb @@ -1,5 +1,7 @@ require 'openssl' require 'faraday' +require 'async' +require 'async/semaphore' OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE @@ -13,20 +15,30 @@ # Тип C: # - работает 1 секунду # - одновременно можно запускать не более одного -# + +A_SEMAPHORE = Async::Semaphore.new(3) +B_SEMAPHORE = Async::Semaphore.new(2) +C_SEMAPHORE = Async::Semaphore.new(1) + def a(value) - puts "https://localhost:9292/a?value=#{value}" - Faraday.get("https://localhost:9292/a?value=#{value}").body + A_SEMAPHORE.acquire do + puts "https://localhost:9292/a?value=#{value}" + Faraday.get("https://localhost:9292/a?value=#{value}").body + end end def b(value) - puts "https://localhost:9292/b?value=#{value}" - Faraday.get("https://localhost:9292/b?value=#{value}").body + B_SEMAPHORE.acquire do + puts "https://localhost:9292/b?value=#{value}" + Faraday.get("https://localhost:9292/b?value=#{value}").body + end end def c(value) - puts "https://localhost:9292/c?value=#{value}" - Faraday.get("https://localhost:9292/c?value=#{value}").body + C_SEMAPHORE.acquire do + puts "https://localhost:9292/c?value=#{value}" + Faraday.get("https://localhost:9292/c?value=#{value}").body + end end # Референсное решение, приведённое ниже работает правильно, занимает ~19.5 секунд @@ -36,43 +48,40 @@ def collect_sorted(arr) arr.sort.join('-') end -start = Time.now - -a11 = a(11) -a12 = a(12) -a13 = a(13) -b1 = b(1) - -ab1 = "#{collect_sorted([a11, a12, a13])}-#{b1}" -puts "AB1 = #{ab1}" - -c1 = c(ab1) -puts "C1 = #{c1}" - -a21 = a(21) -a22 = a(22) -a23 = a(23) -b2 = b(2) - -ab2 = "#{collect_sorted([a21, a22, a23])}-#{b2}" -puts "AB2 = #{ab2}" - -c2 = c(ab2) -puts "C2 = #{c2}" - -a31 = a(31) -a32 = a(32) -a33 = a(33) -b3 = b(3) +def a_collection(number) + Sync do |parent| + [1, 2, 3].map do |n| + parent.async do + a("#{number}#{n}") + end + end.map(&:wait) + end +end -ab3 = "#{collect_sorted([a31, a32, a33])}-#{b3}" -puts "AB3 = #{ab3}" +def part(number) + a_collection, b = Sync do |parent| + a_collection = parent.async { a_collection(number) } + b = parent.async { b(number) } + [a_collection, b].map(&:wait) + end -c3 = c(ab3) -puts "C3 = #{c3}" + ab = "#{collect_sorted(a_collection)}-#{b}" + puts "AB#{number} = #{ab}" -c123 = collect_sorted([c1, c2, c3]) -result = a(c123) + c = c(ab) + puts "C#{number} = #{c}" + c +end -puts "FINISHED in #{Time.now - start}s." -puts "RESULT = #{result}" # 0bbe9ecf251ef4131dd43e1600742cfb +def run + c_collection = Sync do |parent| + [1, 2, 3].map do |n| + parent.async do + part(n) + end + end.map(&:wait) + end + + c123 = collect_sorted(c_collection) + a(c123) +end diff --git a/log.txt b/log.txt new file mode 100644 index 0000000..b332835 --- /dev/null +++ b/log.txt @@ -0,0 +1,24 @@ +https://localhost:9292/a?value=11 +https://localhost:9292/a?value=12 +https://localhost:9292/a?value=13 +https://localhost:9292/b?value=1 +https://localhost:9292/b?value=2 +https://localhost:9292/a?value=21 +https://localhost:9292/a?value=22 +https://localhost:9292/a?value=23 +https://localhost:9292/b?value=3 +AB1 = 6512bd43d9caa6e02c990b0a82652dca-c20ad4d76fe97759aa27a0c99bff6710-c51ce410c124a10e0db5e4b97fc2af39-6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b +https://localhost:9292/c?value=6512bd43d9caa6e02c990b0a82652dca-c20ad4d76fe97759aa27a0c99bff6710-c51ce410c124a10e0db5e4b97fc2af39-6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b +https://localhost:9292/a?value=31 +https://localhost:9292/a?value=32 +https://localhost:9292/a?value=33 +AB2 = 37693cfc748049e45d87b8c7d8b9aacd-3c59dc048e8850243be8079a5c74d079-b6d767d2f8ed5d21a44b0e5886680cb9-d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35 +https://localhost:9292/c?value=37693cfc748049e45d87b8c7d8b9aacd-3c59dc048e8850243be8079a5c74d079-b6d767d2f8ed5d21a44b0e5886680cb9-d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35 +C1 = ee54918569d9620a4947a184c097ab1a90b33db6b8a009e01cbbe7238f800ecf846068a36ac7ae289a0796face8b471dcfd7e7d55f9081bccd8f2f50c1d4f888 +AB3 = 182be0c5cdcd5072bb1864cdee4d3d6e-6364d3f0f495b6ab9dcf8d3b5c6e0b01-c16a5320fa475530d9583c34fd356ef5-4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce +https://localhost:9292/c?value=182be0c5cdcd5072bb1864cdee4d3d6e-6364d3f0f495b6ab9dcf8d3b5c6e0b01-c16a5320fa475530d9583c34fd356ef5-4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce +C2 = 4bc5a33ec7c74626144b5277e887501074b07819ecd9cc506b08e765be1f26a3d7c9fbb59bde46379ca7f0ebf2ef066f38c0a4652d4017a635fc8bdddc070557 +C3 = 35bf8dfb6c327259fd05a56a6d47ecfc21965b0f4ff8926b164c32e31cc91d825da1a73bc145cdec04dfbb0862fb14b687f15a8233be15092c4c85da4b7bf94a +https://localhost:9292/a?value=35bf8dfb6c327259fd05a56a6d47ecfc21965b0f4ff8926b164c32e31cc91d825da1a73bc145cdec04dfbb0862fb14b687f15a8233be15092c4c85da4b7bf94a-4bc5a33ec7c74626144b5277e887501074b07819ecd9cc506b08e765be1f26a3d7c9fbb59bde46379ca7f0ebf2ef066f38c0a4652d4017a635fc8bdddc070557-ee54918569d9620a4947a184c097ab1a90b33db6b8a009e01cbbe7238f800ecf846068a36ac7ae289a0796face8b471dcfd7e7d55f9081bccd8f2f50c1d4f888 +FINISHED in 6.05257s. +RESULT = 0bbe9ecf251ef4131dd43e1600742cfb diff --git a/rspec/client_spec.rb b/rspec/client_spec.rb new file mode 100644 index 0000000..c1b4c97 --- /dev/null +++ b/rspec/client_spec.rb @@ -0,0 +1,29 @@ +require 'rspec' +require 'sinatra/base' +require_relative '../client' + +RSpec.describe 'Client' do + before do + allow(Faraday).to receive(:get).with(any_args) do |*args, **_kwargs| + uri = URI.parse(args[0]) + path = uri.path + value = CGI.parse(uri.query)['value'].first + + result = case path + when '/a' + Digest::MD5.hexdigest(value) + when '/b' + Digest::SHA256.hexdigest(value) + when '/c' + Digest::SHA512.hexdigest(value) + end + + instance_double(Faraday::Response, + status: 200, + body: result, + headers: {'Content-Type' => 'application/json'}) + end + end + + it { expect(run).to eq '0bbe9ecf251ef4131dd43e1600742cfb' } +end diff --git a/runner.rb b/runner.rb new file mode 100644 index 0000000..f35f941 --- /dev/null +++ b/runner.rb @@ -0,0 +1,10 @@ +require_relative 'client' + +$stdout = File.open('log.txt', 'w') + +start = Time.now + +result = run + +puts "FINISHED in #{Time.now - start}s." +puts "RESULT = #{result}" # 0bbe9ecf251ef4131dd43e1600742cfb